VoiletCStudio v1.0 - 紫罗兰 C 工程配置器
功能特性: - JSON 格式工程配置文件 - 虚拟目录管理(类似 MDK) - 跨平台 CMake 自动生成 - 一键编译 Debug/Release - 拖拽文件打开工程 - 实时编译输出显示 作者:虾哥 日期:2026-04-09
This commit is contained in:
293
src/cmakegenerator.cpp
Normal file
293
src/cmakegenerator.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
#include "cmakegenerator.h"
|
||||
#include <QFile>
|
||||
#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";
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
// 编译器配置
|
||||
content += "# 编译器配置\n";
|
||||
if (!config->getCompilerPath().isEmpty() && config->getCompilerPath() != "gcc") {
|
||||
content += "set(CMAKE_C_COMPILER \"" + config->getCompilerPath() + "\")\n";
|
||||
}
|
||||
content += "\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;
|
||||
}
|
||||
39
src/cmakegenerator.h
Normal file
39
src/cmakegenerator.h
Normal file
@@ -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
|
||||
21
src/main.cpp
Normal file
21
src/main.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <QApplication>
|
||||
#include <QStyleFactory>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 设置应用程序信息
|
||||
app.setApplicationName("VoiletCStudio");
|
||||
app.setApplicationVersion("1.0");
|
||||
app.setOrganizationName("LinuxAcme");
|
||||
|
||||
// 设置样式(使用系统原生样式)
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
MainWindow window;
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
855
src/mainwindow.cpp
Normal file
855
src/mainwindow.cpp
Normal file
@@ -0,0 +1,855 @@
|
||||
#include "mainwindow.h"
|
||||
#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 <QDragEnterEvent>
|
||||
#include <QDropEvent>
|
||||
#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: #2196F3; color: white; font-weight: bold; }");
|
||||
connect(openBtn, &QPushButton::clicked, this, &MainWindow::openProject);
|
||||
actionLayout->addWidget(openBtn);
|
||||
|
||||
QPushButton *newBtn = new QPushButton("📄 新建工程");
|
||||
newBtn->setMinimumHeight(35);
|
||||
newBtn->setStyleSheet("QPushButton { background: #4CAF50; color: white; font-weight: bold; }");
|
||||
connect(newBtn, &QPushButton::clicked, this, &MainWindow::newProject);
|
||||
actionLayout->addWidget(newBtn);
|
||||
|
||||
saveButton = new QPushButton("💾 保存工程 (Ctrl+S)");
|
||||
saveButton->setMinimumHeight(35);
|
||||
saveButton->setStyleSheet("QPushButton { background: #FF9800; color: white; font-weight: bold; }");
|
||||
connect(saveButton, &QPushButton::clicked, this, &MainWindow::saveProject);
|
||||
actionLayout->addWidget(saveButton);
|
||||
|
||||
generateButton = new QPushButton("🔨 生成 CMake (Ctrl+G)");
|
||||
generateButton->setMinimumHeight(35);
|
||||
generateButton->setStyleSheet("QPushButton { background: #9C27B0; color: white; font-weight: bold; }");
|
||||
connect(generateButton, &QPushButton::clicked, this, &MainWindow::generateCMake);
|
||||
actionLayout->addWidget(generateButton);
|
||||
|
||||
QPushButton *debugBtn = new QPushButton("🐛 编译 Debug");
|
||||
debugBtn->setMinimumHeight(35);
|
||||
debugBtn->setStyleSheet("QPushButton { background: #00BCD4; color: white; font-weight: bold; }");
|
||||
connect(debugBtn, &QPushButton::clicked, this, &MainWindow::compileDebug);
|
||||
actionLayout->addWidget(debugBtn);
|
||||
|
||||
QPushButton *releaseBtn = new QPushButton("🚀 编译 Release");
|
||||
releaseBtn->setMinimumHeight(35);
|
||||
releaseBtn->setStyleSheet("QPushButton { background: #E91E63; color: white; font-weight: bold; }");
|
||||
connect(releaseBtn, &QPushButton::clicked, this, &MainWindow::compileRelease);
|
||||
actionLayout->addWidget(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("📂 工程路径:同 JSON 配置文件所在目录"));
|
||||
|
||||
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::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasUrls()) {
|
||||
QList<QUrl> urls = event->mimeData()->urls();
|
||||
if (!urls.isEmpty()) {
|
||||
QString file = urls.first().toLocalFile();
|
||||
if (file.endsWith(".json", Qt::CaseInsensitive)) {
|
||||
event->acceptProposedAction();
|
||||
statusBar()->showMessage("📂 释放鼠标打开工程:" + file, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::dropEvent(QDropEvent *event)
|
||||
{
|
||||
if (event->mimeData()->hasUrls()) {
|
||||
QList<QUrl> urls = event->mimeData()->urls();
|
||||
if (!urls.isEmpty()) {
|
||||
QString file = urls.first().toLocalFile();
|
||||
if (file.endsWith(".json", Qt::CaseInsensitive)) {
|
||||
if (!maybeSave()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config->loadConfig(file)) {
|
||||
currentFilePath = file;
|
||||
modified = false;
|
||||
|
||||
projectNameEdit->setText(config->getProjectName());
|
||||
compilerPathEdit->setText(config->getCompilerPath());
|
||||
|
||||
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 &f : it->files) {
|
||||
QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem);
|
||||
fileItem->setText(0, "📄 " + QFileInfo(f).fileName());
|
||||
fileItem->setToolTip(0, f);
|
||||
}
|
||||
}
|
||||
|
||||
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("✅ 工程已加载:" + file, 5000);
|
||||
QMessageBox::information(this, "成功", "工程加载成功!\n" + file);
|
||||
} else {
|
||||
QMessageBox::critical(this, "错误", "无法加载工程文件:" + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, "打开工程", "",
|
||||
"JSON 配置文件 (*.json);;所有文件 (*)");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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, "保存工程", "",
|
||||
"JSON 配置文件 (*.json);;所有文件 (*)");
|
||||
|
||||
if (filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentFilePath = filePath;
|
||||
return saveProject();
|
||||
}
|
||||
|
||||
void MainWindow::generateCMake()
|
||||
{
|
||||
// 先保存工程
|
||||
if (!saveProject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工程目录(JSON 配置文件所在目录)
|
||||
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);
|
||||
process.start("cmake", QStringList() << "..");
|
||||
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);
|
||||
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, "选择源文件", "",
|
||||
"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()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, "选择包含目录");
|
||||
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()
|
||||
{
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, "选择库文件", "",
|
||||
"库文件 (*.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 (!saveProject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentFilePath.isEmpty()) {
|
||||
QMessageBox::warning(this, "警告", "请先保存工程!");
|
||||
return;
|
||||
}
|
||||
|
||||
QString projectDir = QFileInfo(currentFilePath).absolutePath();
|
||||
QString buildDir = projectDir + "/build";
|
||||
|
||||
// 删除旧的 build 目录
|
||||
QDir buildPath(buildDir);
|
||||
if (buildPath.exists()) {
|
||||
buildPath.removeRecursively();
|
||||
}
|
||||
buildPath.mkpath(buildDir);
|
||||
|
||||
// 生成 CMake
|
||||
QString cmakePath = projectDir + "/CMakeLists.txt";
|
||||
if (!cmakeGenerator->generate(config, cmakePath)) {
|
||||
QMessageBox::critical(this, "错误", "生成 CMake 失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建编译信息对话框
|
||||
QDialog *compileDialog = new QDialog(this);
|
||||
compileDialog->setWindowTitle("编译 " + buildType);
|
||||
compileDialog->resize(800, 600);
|
||||
|
||||
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: #f44336; color: white; font-weight: bold; font-size: 14px; }");
|
||||
dialogLayout->addWidget(closeBtn);
|
||||
|
||||
compileDialog->show();
|
||||
|
||||
// 执行 cmake
|
||||
outputEdit->append("📋 正在配置 CMake...\n");
|
||||
QProcess cmakeProcess;
|
||||
cmakeProcess.setWorkingDirectory(buildDir);
|
||||
cmakeProcess.start("cmake", QStringList() << "-DCMAKE_BUILD_TYPE=" + buildType << "..");
|
||||
cmakeProcess.waitForFinished(30000);
|
||||
|
||||
outputEdit->append(cmakeProcess.readAllStandardOutput());
|
||||
QString cmakeError = cmakeProcess.readAllStandardError();
|
||||
if (!cmakeError.isEmpty()) {
|
||||
outputEdit->append("⚠️ CMake 警告/错误:\n" + cmakeError);
|
||||
}
|
||||
|
||||
// 执行 make
|
||||
outputEdit->append("\n🔨 正在编译 " + buildType + "...\n");
|
||||
QProcess makeProcess;
|
||||
makeProcess.setWorkingDirectory(buildDir);
|
||||
makeProcess.start("make", 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;");
|
||||
}
|
||||
|
||||
connect(closeBtn, &QPushButton::clicked, compileDialog, &QDialog::accept);
|
||||
}
|
||||
|
||||
104
src/mainwindow.h
Normal file
104
src/mainwindow.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#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();
|
||||
|
||||
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();
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
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;
|
||||
|
||||
// 配置和生成器
|
||||
ProjectConfig *config;
|
||||
CMakeGenerator *cmakeGenerator;
|
||||
|
||||
QString currentFilePath;
|
||||
bool modified;
|
||||
bool showCMakeSuccessMsg; // 控制是否显示 CMake 成功弹窗
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
351
src/projectconfig.cpp
Normal file
351
src/projectconfig.cpp
Normal file
@@ -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();
|
||||
}
|
||||
101
src/projectconfig.h
Normal file
101
src/projectconfig.h
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user