当前位置: 首页 > article >正文

【学写LibreCAD】1 LibreCAD主程序

一、源码

  • 头文件:
#ifndef MAIN_H
#define MAIN_H

#include<QStringList>

#define STR(x)   #x
#define XSTR(x)  STR(x)

/**
 * @brief handleArgs
 * @param argc cli argument counter from main()
 * @param argv cli arguments from main()
 * @param argClean a list of indices to be ignored
 * @return
 */
QStringList handleArgs(int argc, char** argv, const QList<int>& argClean);

/**
 * @brief LCReleaseLabel return a label for the current release based on LC_VERSION in src.pro
 * @return "Release Candidate" - if LC_VERSION contains rc;
 *         "BETA" - if LC_VERSION contains beta
 *         "ALPHA" - if LC_VERSION contains alpha
 */
QString LCReleaseLabel();

#endif

程序文件:

#include <clocale>

#include <QApplication>
#include <QByteArray>
#include <QDebug>
#include <QFileInfo>
#include <QMessageBox>
#include <QPainter>
#include <QPixmap>
#include <QSettings>
#include <QSplashScreen>

#include "console_dxf2pdf.h"
#include "console_dxf2png.h"
#include "lc_application.h"
#include "main.h"
#include "qc_applicationwindow.h"
#include "qg_dlginitial.h"
#include "rs_debug.h"
#include "rs_fontlist.h"
#include "rs_patternlist.h"
#include "rs_settings.h"
#include "rs_system.h"

namespace{
    void restoreWindowGeometry(QC_ApplicationWindow& appWin, QSettings& settings);
// update splash for alpha/beta names)
    void updateSplash(const std::unique_ptr<QSplashScreen>& splash);
}
/**
 * Main. 创建应用程序窗口。
 */
 // fixme - sand - refactor and split to several specialized functions
int main(int argc, char** argv){
    QT_REQUIRE_VERSION(argc, argv, "5.2.1"); //确保 LibreCAD 在 Qt 5.2.1 或更高版本上运行

    //检查前两个参数,以决定我们是要将librecad作为控制台dxf2pdf还是dxf2png工具运行。在Linux上,我们可以创建一个指向librecad可执行文件的链接,并将其命名为dxf2pdf。因此,我们可以运行以下任一操作:
    //librecad dxf2pdf[选项]...
    //或者只是:dxf2pdf[选项]...

    for (int i = 0; i < qMin(argc, 2); i++) {
        QString arg(argv[i]);
        if (i == 0) {
            arg = QFileInfo(QFile::decodeName(argv[i])).baseName();
        }
        if (arg.compare("dxf2pdf") == 0) {
            return console_dxf2pdf(argc, argv);
        }
        if (arg.compare("dxf2png") == 0 || arg == "dxf2svg") {
            return console_dxf2png(argc, argv);
        }
    }

    RS_DEBUG->setLevel(RS_Debug::D_WARNING);

    LC_Application app(argc, argv);
    QCoreApplication::setOrganizationName("LibreCAD");
    QCoreApplication::setApplicationName("LibreCAD");
    QCoreApplication::setApplicationVersion(XSTR(LC_VERSION));

    RS_Settings::init(app.organizationName(), app.applicationName());

    QGuiApplication::setDesktopFileName("librecad.desktop");

    QSettings settings; // fixme - direct invocation of settings

    bool first_load = settings.value("Startup/FirstLoad", 1).toBool();

    const QString lpDebugSwitch0("-d"),lpDebugSwitch1("--debug") ;
    const QString help0("-h"), help1("--help");
    bool allowOptions=true;
    QList<int> argClean;
    for (int i=0; i<argc; i++){
        QString argstr(argv[i]);
        if(allowOptions&&QString::compare("--", argstr)==0){
            allowOptions=false;
            continue;
        }
        if (allowOptions && (help0.compare(argstr, Qt::CaseInsensitive)==0 ||
                             help1.compare(argstr, Qt::CaseInsensitive)==0 )){
            qDebug()<<"Usage: librecad [command] <options> <dxf file>";
            qDebug()<<"";
            qDebug()<<"Commands:";
            qDebug()<<"";
            qDebug()<<"  dxf2pdf\tRun librecad as console dxf2pdf tool. Use -h for help.";
            qDebug()<<"  dxf2png\tRun librecad as console dxf2png tool. Use -h for help.";
            qDebug()<<"  dxf2svg\tRun librecad as console dxf2svg tool. Use -h for help.";
            qDebug()<<"";
            qDebug()<<"Options:";
            qDebug()<<"";
            qDebug()<<"  -h, --help\tdisplay this message";
            qDebug()<<"  -d, --debug <level>";
            qDebug()<<"";
            RS_DEBUG->print( RS_Debug::D_NOTHING, "possible debug levels:");
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Nothing", RS_Debug::D_NOTHING);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Critical", RS_Debug::D_CRITICAL);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Error", RS_Debug::D_ERROR);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Warning", RS_Debug::D_WARNING);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Notice", RS_Debug::D_NOTICE);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Informational", RS_Debug::D_INFORMATIONAL);
            RS_DEBUG->print( RS_Debug::D_NOTHING, "    %d Debugging", RS_Debug::D_DEBUGGING);
            exit(0);
        }
        if (allowOptions&& (argstr.startsWith(lpDebugSwitch0, Qt::CaseInsensitive) ||
                             argstr.startsWith(lpDebugSwitch1, Qt::CaseInsensitive) )){
            argClean<<i;

            // to control the level of debugging output use --debug with level 0-6, e.g. --debug3
            // for a list of debug levels use --debug?
            // if no level follows, the debugging level is set
            argstr.remove(QRegularExpression("^"+lpDebugSwitch0));
            argstr.remove(QRegularExpression("^"+lpDebugSwitch1));
            char level;
            if(argstr.size()==0){
                if(i+1<argc){
                    if(QRegularExpression(R"(\d*)").match(argv[i+1]).hasMatch()){
                        ++i;
                        qDebug()<<"reading "<<argv[i]<<" as debugging level";
                        level=argv[i][0];
                        argClean<<i;
                    } else {
                        level = '3';
                    }
                }
                else {
                    level = '3'; //default to D_WARNING
                }
            }
            else {
                level = argstr.toStdString()[0];
            }

            switch(level){
                case '?' : {
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "possible debug levels:");
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Nothing", RS_Debug::D_NOTHING);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Critical", RS_Debug::D_CRITICAL);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Error", RS_Debug::D_ERROR);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Warning", RS_Debug::D_WARNING);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Notice", RS_Debug::D_NOTICE);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Informational", RS_Debug::D_INFORMATIONAL);
                    RS_DEBUG->print(RS_Debug::D_NOTHING, "    %d Debugging", RS_Debug::D_DEBUGGING);
                    return 0;
                }
                case '0' + RS_Debug::D_NOTHING : {
                    RS_DEBUG->setLevel(RS_Debug::D_NOTHING);
                    break;
                }
                case '0' + RS_Debug::D_CRITICAL : {
                    RS_DEBUG->setLevel(RS_Debug::D_CRITICAL);
                    break;
                }
                case '0' + RS_Debug::D_ERROR : {
                    RS_DEBUG->setLevel(RS_Debug::D_ERROR);
                    break;
                }
                case '0' + RS_Debug::D_WARNING : {
                    RS_DEBUG->setLevel(RS_Debug::D_WARNING);
                    break;
                }
                case '0' + RS_Debug::D_NOTICE : {
                    RS_DEBUG->setLevel(RS_Debug::D_NOTICE);
                    break;
                }
                case '0' + RS_Debug::D_INFORMATIONAL : {
                    RS_DEBUG->setLevel(RS_Debug::D_INFORMATIONAL);
                    break;
                }
                case '0' + RS_Debug::D_DEBUGGING : {
                    RS_DEBUG->setLevel(RS_Debug::D_DEBUGGING);
                    break;
                }
                default : {
                    RS_DEBUG->setLevel(RS_Debug::D_DEBUGGING);
                    break;
                }
            }
        }
    }
    RS_DEBUG->print("param 0: %s", argv[0]);

    QFileInfo prgInfo( QFile::decodeName(argv[0]) );
    QString prgDir(prgInfo.absolutePath());

    RS_SYSTEM->init(app.applicationName(), app.applicationVersion(), XSTR(QC_APPDIR), prgDir);

    // parse command line arguments that might not need a launched program:
    QStringList fileList = handleArgs(argc, argv, argClean);

    QString unit = settings.value("Defaults/Unit", "Invalid").toString();

    // show initial config dialog:
    if (first_load){
        RS_DEBUG->print("main: show initial config dialog..");
        QG_DlgInitial di(nullptr);
        QPixmap pxm(":/main/intro_librecad.png");
        di.setPixmap(pxm);
        if (di.exec()) {
            unit = LC_GET_ONE_STR("Defaults", "Unit", "None");
        }
        RS_DEBUG->print("main: show initial config dialog: OK");
    }

    auto splash = std::make_unique<QSplashScreen>();

    bool show_splash = settings.value("Startup/ShowSplash", 1).toBool();

    if (show_splash){
        updateSplash(splash);
        app.processEvents();
        RS_DEBUG->print("main: splashscreen: OK");
    }

    RS_DEBUG->print("main: init fontlist..");
    RS_FONTLIST->init();
    RS_DEBUG->print("main: init fontlist: OK");

    RS_DEBUG->print("main: init patternlist..");
    RS_PATTERNLIST->init();
    RS_DEBUG->print("main: init patternlist: OK");

    RS_DEBUG->print("main: loading translation..");

    settings.beginGroup("Appearance");
    QString lang = settings.value("Language", "en").toString();
    QString langCmd = settings.value("LanguageCmd", "en").toString();
    settings.endGroup();

    RS_SYSTEM->loadTranslation(lang, langCmd);
    RS_DEBUG->print("main: loading translation: OK");

    RS_DEBUG->print("main: creating main window..");
    QC_ApplicationWindow& appWin = *QC_ApplicationWindow::getAppWindow();
#ifdef Q_OS_MAC
    app.installEventFilter(&appWin);
#endif
    RS_DEBUG->print("main: setting caption");
    appWin.setWindowTitle(app.applicationName());

    RS_DEBUG->print("main: show main window");

    settings.beginGroup("Defaults");
    if( !settings.contains("UseQtFileOpenDialog")) {
#ifdef Q_OS_LINUX
        // on Linux don't use native file dialog
        // because of case insensitive filters (issue #791)
        settings.setValue("UseQtFileOpenDialog", QVariant(1));
#else
        settings.setValue("UseQtFileOpenDialog", QVariant(0));
#endif
    }
    settings.endGroup();

    if (!first_load) {
        restoreWindowGeometry(appWin, settings);
    }

    bool maximize = settings.value("Startup/Maximize", 0).toBool();

    if (maximize || first_load) {
        appWin.showMaximized();
    }
    else {
        appWin.show();
    }

    RS_DEBUG->print("main: set focus");
    appWin.setFocus();
    RS_DEBUG->print("main: creating main window: OK");

    if (show_splash){
        RS_DEBUG->print("main: updating splash");
        splash->raise();
        splash->showMessage(QObject::tr("Loading..."),
                Qt::AlignRight|Qt::AlignBottom, Qt::black);
        RS_DEBUG->print("main: processing events");
        qApp->processEvents();
        RS_DEBUG->print("main: updating splash: OK");
    }

    // Set LC_NUMERIC so that entering numeric values uses . as the decimal separator
    setlocale(LC_NUMERIC, "C");

    RS_DEBUG->print("main: loading files..");
#ifdef Q_OS_MAC
    // get the file list from LC_Application
    fileList << app.fileList();
#endif

    // reopen files that we open during last close of application
    // we'll reopen them if no explicit files to open are provided in command line
    bool reopenLastFiles;
    QString lastFiles;
    QString activeFile;
    LC_GROUP("Startup");
    {
        reopenLastFiles = LC_GET_BOOL("OpenLastOpenedFiles");
        lastFiles = LC_GET_STR("LastOpenFilesList", "");
        activeFile = LC_GET_STR("LastOpenFilesActive", "");
        bool checkForNewVersion = LC_GET_BOOL("CheckForNewVersions", true);

        if (reopenLastFiles && fileList.isEmpty() && !lastFiles.isEmpty()) {
                foreach(const QString &filename, lastFiles.split(";")) {
                    if (!filename.isEmpty() && QFileInfo::exists(filename))
                        fileList << filename;
                }
        }

        bool files_loaded = false;
        for (QStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it) {
            if (show_splash) {
                splash->showMessage(QObject::tr("Loading File %1..")
                                        .arg(QDir::toNativeSeparators(*it)),
                                    Qt::AlignRight | Qt::AlignBottom, Qt::black);
                qApp->processEvents();
            }
            appWin.slotFileOpen(*it);
            files_loaded = true;
        }

        if (reopenLastFiles) {
            appWin.activateWindowWithFile(activeFile);
        }
        RS_DEBUG->print("main: loading files: OK");

        if (!files_loaded) {
            appWin.slotFileNewNew();
        }

        if (show_splash) {
            splash->finish(&appWin);
            splash.release();
        }


        if (checkForNewVersion) {
            appWin.checkForNewVersion();
        }
    }
    LC_GROUP_END();
    if (first_load)
        settings.setValue("Startup/FirstLoad", 0);

    RS_DEBUG->print("main: entering Qt event loop");

    QCoreApplication::processEvents();

    int return_code = app.exec();

    RS_DEBUG->print("main: exited Qt event loop");

    // Destroy the singleton
    QC_ApplicationWindow::getAppWindow().reset();

    return return_code;
}

/**
 * Handles command line arguments that might not require a GUI.
 *
 * @return list of files to load on startup.
 */
QStringList handleArgs(int argc, char** argv, const QList<int>& argClean){
    RS_DEBUG->print("main: handling args..");
    QStringList ret;

    bool doexit = false;

    for (int i=1; i<argc; i++)    {
        if(argClean.indexOf(i)>=0) continue;
        if (!QString(argv[i]).startsWith("-"))
        {
            QString fname = QDir::toNativeSeparators(
            QFileInfo(QFile::decodeName(argv[i])).absoluteFilePath());
            ret.append(fname);
        }
        else if (QString(argv[i])=="--exit"){
            doexit = true;
        }
    }
    if (doexit)    {
        exit(0);
    }
    RS_DEBUG->print("main: handling args: OK");
    return ret;
}

QString LCReleaseLabel(){
    QString version{XSTR(LC_VERSION)};
    QString label;
    const std::map<QString, QString> labelMap = {
        {"rc", QObject::tr("Release Candidate")},
        {"beta", QObject::tr("BETA")},
        {"alpha", QObject::tr("ALPHA")}
    };
    for (const auto& [key, value]: labelMap) {
        if (version.contains(key, Qt::CaseInsensitive)) {
            label=value;
            break;
        }
    }
    return label;
}

namespace {
    void restoreWindowGeometry(QC_ApplicationWindow& appWin, QSettings& settings){
        settings.beginGroup("Geometry");
        auto geometryB64 = settings.value("/WindowGeometry").toString().toUtf8();
        auto geometry = QByteArray::fromBase64(geometryB64, QByteArray::Base64Encoding);
        if (!geometry.isEmpty()) {
            appWin.restoreGeometry(geometry);
        } else {
            // fallback
            int windowWidth = settings.value("WindowWidth", 1024).toInt();
            int windowHeight = settings.value("WindowHeight", 1024).toInt();
            int windowX = settings.value("WindowX", 32).toInt();
            int windowY = settings.value("WindowY", 32).toInt();
            appWin.resize(windowWidth, windowHeight);
            appWin.move(windowX, windowY);
        }

        settings.endGroup();
    }


// Update Splash image to show "ALPHA", "BETA", and "Release Candidate"
QPixmap getSplashImage(const std::unique_ptr<QSplashScreen>& splash, const QString& label);
// Update Splash Screen
    void updateSplash(const std::unique_ptr<QSplashScreen>& splash){
        if (splash == nullptr)
            return;

    QString label = LCReleaseLabel();
        if (label.isEmpty())
            return;

        QPixmap splashImage = getSplashImage(splash, label);
        splash->setPixmap(splashImage);
        splash->setAttribute(Qt::WA_DeleteOnClose);
        splash->show();
        splash->showMessage(QObject::tr("Loading.."),
                            Qt::AlignRight|Qt::AlignBottom, Qt::black);
    }

// Update Splash image to show "ALPHA", "BETA", and "Release Candidate"
    QPixmap getSplashImage(const std::unique_ptr<QSplashScreen>& splash, const QString& label){
        if (splash == nullptr)
            return {};

        QPixmap pixmapSplash(":/main/splash_librecad.png");
        QPainter painter(&pixmapSplash);
        const double factorX = pixmapSplash.width()/542.;
        const double factorY = pixmapSplash.height()/337.;
        painter.setPen(QColor(255, 0, 0, 128));
        QRectF labelRect{QPointF{280.*factorX, 130.*factorY}, QPointF{480.*factorX, 170.*factorY}};
        QFont font;
        font.setPixelSize(int(labelRect.height()) - 2);
        painter.setFont(font);
        painter.drawText(labelRect,Qt::AlignRight, label);
        return pixmapSplash;
    }
}

二、代码介绍

  1. Qt 版本检查
QT_REQUIRE_VERSION(argc, argv, "5.2.1");

确保应用程序运行在 Qt 5.2.1 或更高版本上。

  1. 控制台模式检测
for (int i = 0; i < qMin(argc, 2); i++) {
    // 检查是否为 dxf2pdf/dxf2png 命令
    if (arg.compare("dxf2pdf") == 0) return console_dxf2pdf(...);
    if (arg.compare("dxf2png") == 0) return console_dxf2png(...);
}

检测是否以命令行工具模式运行,用于文件格式转换(如 PDF/PNG/SVG)。

  1. 应用程序初始化
LC_Application app(argc, argv);
QCoreApplication::setOrganizationName("LibreCAD");
// ... 其他应用程序设置 ...
RS_Settings::init(...);
  • 创建 Qt 应用程序对象。

  • 设置组织和应用程序的元数据。

  • 初始化持久化设置系统。

  1. 命令行参数处理
QStringList fileList = handleArgs(argc, argv, argClean);

处理以下参数:

  • 调试级别控制(-d/–debug)。
  • 帮助信息输出(-h/–help)。
  • 要打开的文件路径。
  • 特殊命令(如 --exit)。
  1. 首次运行配置
if (first_load) {
    QG_DlgInitial di(nullptr);
    // 显示初始配置对话框
}
  • 如果是第一次运行,显示初始设置对话框。
  • 设置默认单位和其他基本偏好。
  1. 启动画面设置
auto splash = std::make_unique<QSplashScreen>();
updateSplash(splash); // 添加版本标签(如 ALPHA/BETA 等)
  • 创建并自定义启动画面。
  • 使用 getSplashImage() 添加版本标签。
  1. 资源初始化
RS_FONTLIST->init();  // 字体
RS_PATTERNLIST->init(); // 图案
RS_SYSTEM->loadTranslation(...); // 本地化
  • 加载 CAD 所需的资源。
  • 设置多语言支持。
  1. 主窗口创建
QC_ApplicationWindow& appWin = *QC_ApplicationWindow::getAppWindow();
restoreWindowGeometry(appWin, settings);
  • 使用单例模式创建主应用程序窗口。
  • 从设置中恢复窗口的几何状态。
  1. 文件处理
// 如果配置了,重新打开上次的文件
if (reopenLastFiles) fileList = lastFiles.split(";"); 

// 加载命令行指定的文件
for (QString file : fileList) appWin.slotFileOpen(file);
  • 处理命令行指定的文件以及“重新打开上次文件”的功能。
  • 如果没有指定文件,则创建新文档。
  1. 事件循环与清理
int return_code = app.exec();
// ... 清理 ...
return return_code;
  • 启动 Qt 事件循环。
  • 在退出时进行适当的清理。
  • 在关闭时保存设置。
关键架构特性
  1. 单例模式:用于主应用程序窗口(QC_ApplicationWindow::getAppWindow())。
  2. 工厂模式:用于字体和图案资源的初始化。
  3. 命令分发:同时支持 GUI 和命令行模式。
  4. 持久化:使用 QSettings 保存窗口几何状态和偏好设置。
  5. 国际化:通过翻译文件支持多语言。
重要的辅助函数
  1. handleArgs():处理命令行参数。
  2. restoreWindowGeometry():从设置中恢复窗口状态。
  3. updateSplash():自定义启动画面,添加版本信息。
  4. LCReleaseLabel():确定版本类型(如 Alpha/Beta 等)。
总结

这段代码展示了一个成熟的 Qt 应用程序结构,具有清晰的职责分离:

  • 初始化:应用程序和资源的初始化。
  • 资源管理:字体、图案和本地化资源的加载。
  • UI 设置:主窗口和启动画面的创建与配置。
  • 命令行处理:支持命令行模式和文件加载。
    通过良好的架构设计和模块化实现,LibreCAD 的主程序能够高效地处理多种运行场景,并为用户提供一致的使用体验。

http://www.kler.cn/a/563145.html

相关文章:

  • angular使用IndexedDb实现增删改查sql
  • 【实战中提升自己】防火墙篇之双ISP切换与VRRP切换对于用户的体验
  • Sublime Text4安装、汉化
  • 每日一题——字母异位词分组
  • 刚充值Deepseek账号,但接入官方的API却遇到了问题【VSCode Cline Cursor Deepseek deepseek-reasoner】
  • Uniapp 小程序:语音播放与暂停功能的实现及优化方案
  • Flink Checkpoint机制详解
  • 数据存储:一文掌握存储数据到MongoDB详解
  • DS-3KM220250226 3K引擎修复版传奇2025版完整源码搭建教程
  • JAVA面试_进阶部分_Linux面试题
  • 【Uniapp-Vue3】登录成功后获取当前用户信息并渲染到页面中
  • JDBC连接池
  • jar生产部署脚本
  • 使用ZFile打造属于自己的私有云系统结合内网穿透实现安全远程访问
  • OpenHarmony DFX子系统
  • seasms v9 注入漏洞 + order by注入+​information_schema​解决方法
  • Gtest, Junit,以及pytest对比理解
  • 轻量化网络设计|ShuffleNet:深度学习中的轻量化革命
  • 嵌入式的应用领域和发展趋势
  • 什么是Sass,如何使用?