v1.2: .VSC 文件格式 & 跨平台安装程序

【文件格式】
- 工程文件扩展名从 .json 改为 .VSC(内容仍为 JSON)
- 文件对话框过滤、保存/打开全部适配 .VSC
- 双击 .VSC 文件可直接打开工程(命令行传参)

【安装程序】
- Windows: NSIS 安装脚本 (installer/voiletcstudio.nsi)
  支持注册 .VSC 文件关联、开始菜单/桌面快捷方式、卸载
- Linux: install.sh/uninstall.sh 一键安装/卸载
  自动注册 MIME 类型、.desktop 文件、命令行软链接
- 内置 --register/--unregister 命令行参数用于文件关联

【代码清理】
- 移除所有拖拽功能(dragEnter/Move/DropEvent)
- 新增 MainWindow::openProjectFile() 用于命令行打开
- main.cpp 完整重写,加入命令行解析器

【其他】
- .gitignore 排除 NSIS 输出 (.exe)
This commit is contained in:
虾哥
2026-04-28 19:14:54 +08:00
parent 8616776a24
commit 2ea81633f9
8 changed files with 419 additions and 127 deletions

5
.gitignore vendored
View File

@@ -12,3 +12,8 @@ VoiletCStudio
.idea/
*.user
*.pro.user
# Installer output
*.exe
VoiletCStudio-Setup-*.exe
installer/build/

92
installer/install.sh Normal file
View File

@@ -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 ""

25
installer/uninstall.sh Normal file
View File

@@ -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} 已卸载"

View File

@@ -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

View File

@@ -4,7 +4,8 @@ Name=VoiletCStudio
Name[zh_CN]=紫罗兰 C 工程配置器
Comment=C Project Configurator with CMake
Comment[zh_CN]=C 语言工程配置器,自动生成 CMakeLists.txt
Exec=VoiletCStudio
Exec=VoiletCStudio %f
Icon=voiletcstudio
MimeType=application/x-voiletcstudio;
Categories=Development;IDE;
Terminal=false

View File

@@ -1,8 +1,125 @@
#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);
@@ -12,14 +129,49 @@ int main(int argc, char *argv[])
// 设置应用程序信息
app.setApplicationName("VoiletCStudio");
app.setApplicationVersion("1.1");
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();
}

View File

@@ -1,5 +1,6 @@
#include "mainwindow.h"
#include <QApplication>
#include <QFile>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
@@ -18,9 +19,6 @@
#include <QProcess>
#include <QDir>
#include <QScrollBar>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
MainWindow::MainWindow(QWidget *parent)
@@ -31,9 +29,6 @@ MainWindow::MainWindow(QWidget *parent)
config = new ProjectConfig(this);
cmakeGenerator = new CMakeGenerator(this);
// ★ 启用拖拽
setAcceptDrops(true);
setupUI();
setupConnections();
updateWindowTitle();
@@ -109,7 +104,7 @@ void MainWindow::setupUI()
basicLayout->addWidget(projectNameEdit);
basicLayout->addStretch();
basicLayout->addWidget(new QLabel("📂 工程路径:同 JSON 配置文件所在目录"));
basicLayout->addWidget(new QLabel("📂 工程路径:同 VSC 工程文件所在目录"));
mainLayout->addWidget(basicGroup);
@@ -280,22 +275,6 @@ void MainWindow::setupUI()
// 状态栏
statusBar()->showMessage("就绪");
// ★ 禁用子控件的拖拽,让所有拖拽事件由 MainWindow 统一处理
sourceTree->setAcceptDrops(false);
sourceTree->viewport()->setAcceptDrops(false);
includeDirList->setAcceptDrops(false);
includeDirList->viewport()->setAcceptDrops(false);
libraryList->setAcceptDrops(false);
libraryList->viewport()->setAcceptDrops(false);
defineList->setAcceptDrops(false);
defineList->viewport()->setAcceptDrops(false);
optionList->setAcceptDrops(false);
optionList->viewport()->setAcceptDrops(false);
projectNameEdit->setAcceptDrops(false);
compilerPathEdit->setAcceptDrops(false);
assemblerPathEdit->setAcceptDrops(false);
linkerPathEdit->setAcceptDrops(false);
}
void MainWindow::setupConnections()
@@ -361,99 +340,6 @@ bool MainWindow::maybeSave()
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);
return;
}
}
}
event->ignore();
}
void MainWindow::dragMoveEvent(QDragMoveEvent *event)
{
// 拖拽移动时也要接受,否则 Qt 不会触发 dropEvent
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();
return;
}
}
}
event->ignore();
}
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;
// 更新 UI
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 &f : it->files) {
QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem);
fileItem->setText(0, "📄 " + QFileInfo(f).fileName());
fileItem->setToolTip(0, f);
}
}
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("✅ 工程已加载:" + file, 5000);
} else {
QMessageBox::critical(this, "错误", "无法加载工程文件:" + file);
}
}
}
}
}
void MainWindow::newProject()
{
if (!maybeSave()) {
@@ -485,7 +371,7 @@ void MainWindow::openProject()
}
QString filePath = QFileDialog::getOpenFileName(this, "打开工程", "",
"JSON 配置文件 (*.json);;所有文件 (*)");
"VSC 工程文件 (*.VSC);;所有文件 (*)");
if (filePath.isEmpty()) {
return;
@@ -530,6 +416,54 @@ void MainWindow::openProject()
}
}
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()) {
@@ -555,7 +489,7 @@ bool MainWindow::saveProject()
bool MainWindow::saveProjectAs()
{
QString filePath = QFileDialog::getSaveFileName(this, "保存工程", "",
"JSON 配置文件 (*.json);;所有文件 (*)");
"VSC 工程文件 (*.VSC);;所有文件 (*)");
if (filePath.isEmpty()) {
return false;
@@ -572,7 +506,7 @@ void MainWindow::generateCMake()
return;
}
// 获取工程目录(JSON 配置文件所在目录)
// 获取工程目录(VSC 工程文件所在目录)
QString projectDir = QFileInfo(currentFilePath).absolutePath();
QString buildDir = projectDir + "/build";
QString cmakePath = projectDir + "/CMakeLists.txt";

View File

@@ -19,6 +19,9 @@ class MainWindow : public QMainWindow
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
// 通过命令行/双击打开工程文件
void openProjectFile(const QString &filePath);
private slots:
// 文件操作
@@ -67,11 +70,6 @@ private slots:
void browseAssemblerPath();
void browseLinkerPath();
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
void setupUI();
void setupConnections();