diff --git a/CMakeLists.txt b/CMakeLists.txt index d319924f4ddea3444f2956d057a5a7791cc5a751..c2491a86a77394363e5e0ca650dbacb6160b312b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,92 +1,131 @@ -cmake_minimum_required(VERSION 3.25) +# 指定构建此项目所需的最低 CMake 版本为 3.25 +cmake_minimum_required(VERSION 3.5) +# 将自定义的 CMake 模块目录添加到 CMake 模块搜索路径中 +# 这样 CMake 就能在这些目录下查找自定义的模块文件 list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_SOURCE_DIR}/cmake/modules - ${CMAKE_SOURCE_DIR}/cmake/find-modules + # ${CMAKE_SOURCE_DIR}/cmake/find-modules ) +# 若未显式设置构建类型,且项目不支持多配置构建(如 Visual Studio 解决方案) +# 则将构建类型默认设置为 Release 模式,并将可选的构建类型限定为 Debug 和 Release if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release") endif() +# 让 CMake 在检查依赖项时保持静默,不输出详细信息 set(CMAKE_REQUIRED_QUIET ON) +# 设置 CMake 策略的最低版本为 3.15 set(CMAKE_POLICY_VERSION_MINIMUM 3.15) -####################################################### -# Custom Build Configuration -####################################################### +# ####################################################### +# # Custom Build Configuration +# ####################################################### -include(CustomOptions) +# # 包含自定义选项文件,该文件可能定义了一些自定义的构建选项 +# # include(CustomOptions) -if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/custom) - message(STATUS "Enabling custom build") - set(QGC_CUSTOM_BUILD ON) - list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/custom/cmake) - include(CustomOverrides) -endif() +# # 检查项目源目录下是否存在 custom 目录 +# # 若存在,则启用自定义构建,并将 custom/cmake 目录添加到模块搜索路径 +# # 同时包含自定义覆盖文件 +# # if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/custom) +# # message(STATUS "Enabling custom build") +# # set(QGC_CUSTOM_BUILD ON) +# # list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/custom/cmake) +# # include(CustomOverrides) +# # endif() -####################################################### -# Project Info -####################################################### +# ####################################################### +# # Project Info +# ####################################################### -# include(Prechecks) +# 注释掉的代码,可能用于项目构建前的预检查 +include(Prechecks) +# 注释掉的代码,用于设置 macOS 或 iOS 构建的架构和系统根目录 # set(CMAKE_OSX_ARCHITECTURES "arm64") # set(CMAKE_OSX_SYSROOT "iphoneos") -if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") -endif() +# 若在 Apple 平台上构建,设置 macOS 部署目标版本为 12.0 +#if(APPLE) +# set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") +#endif() + +# 包含 Git 相关的 CMake 模块,可能用于获取 Git 版本信息等 include(Git) -project(${QGC_APP_NAME} +# 定义项目信息,包括项目名称、版本、描述、主页 URL 以及使用的编程语言 +project( + QGroundControl VERSION ${QGC_APP_VERSION} - DESCRIPTION ${QGC_APP_DESCRIPTION} - HOMEPAGE_URL ${QGC_ORG_DOMAIN} + # DESCRIPTION ${QGC_APP_DESCRIPTION} + # HOMEPAGE_URL ${QGC_ORG_DOMAIN} LANGUAGES C CXX ) -if(APPLE AND NOT IOS) - set(MACOS TRUE) +# 若在 macOS 平台(非 iOS)构建,设置 MACOS 变量为 ON +# 并检查是否为通用构建(同时支持 arm64 和 x86_64 架构) +#if(APPLE AND NOT IOS) +# set(MACOS TRUE) - # CMAKE_APPLE_SILICON_PROCESSOR - if("${CMAKE_OSX_ARCHITECTURES}" MATCHES "arm64;x86_64" OR "${CMAKE_OSX_ARCHITECTURES}" MATCHES "x86_64;arm64") - set(MACOS_UNIVERSAL_BUILD ON) - endif() -endif() +# # CMAKE_APPLE_SILICON_PROCESSOR +# if("${CMAKE_OSX_ARCHITECTURES}" MATCHES "arm64;x86_64" OR "${CMAKE_OSX_ARCHITECTURES}" MATCHES "x86_64;arm64") +# set(MACOS_UNIVERSAL_BUILD ON) +# endif() +#endif() ####################################################### # CMake Configuration Options ####################################################### +# 包含 GNU 安装目录标准模块,用于设置安装目录的变量 include(GNUInstallDirs) +# 包含 FetchContent 模块,用于在构建过程中获取外部依赖项 include(FetchContent) +# 包含 CMake 打印帮助模块,可用于打印变量信息 include(CMakePrintHelpers) +# 检查是否存在自定义的 CPM 源缓存目录 +# 若存在,则设置 CPM 源缓存环境变量;否则,使用构建目录下的 cpm_modules 目录 if(EXISTS "${QGC_CPM_SOURCE_CACHE}") set(ENV{CPM_SOURCE_CACHE} "${QGC_CPM_SOURCE_CACHE}") else() set(ENV{CPM_SOURCE_CACHE} "${CMAKE_BINARY_DIR}/cpm_modules") endif() +# 包含 CPM 模块,用于管理项目依赖 include(CPM) +# 设置 C++ 标准为 C++20 set(CMAKE_CXX_STANDARD 20) +# 要求必须使用指定的 C++ 标准 set(CMAKE_CXX_STANDARD_REQUIRED ON) +# 不使用编译器特定的 C++ 扩展 set(CMAKE_CXX_EXTENSIONS OFF) +# 开启 Qt 的自动元对象编译器(MOC)功能 set(CMAKE_AUTOMOC ON) +# 开启 Qt 的自动用户界面编译器(UIC)功能 set(CMAKE_AUTOUIC ON) +# 开启 Qt 的自动资源编译器(RCC)功能 set(CMAKE_AUTORCC ON) +# 让 CMake 输出带颜色的诊断信息,便于查看 set(CMAKE_COLOR_DIAGNOSTICS ON) +# 注释掉的代码,可能用于导出构建数据库,但会导致配置错误 # set(CMAKE_EXPORT_BUILD_DATABASE ON) # Causes Configuration Error? +# 生成 compile_commands.json 文件,供 IDE 或静态分析工具使用 set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# 自动将当前源目录和二进制目录添加到包含路径 set(CMAKE_INCLUDE_CURRENT_DIR ON) +# 生成位置无关代码,适用于共享库 set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# 注释掉的代码,用于开启跨过程优化 # set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) +# 若安装前缀为默认值,则根据不同平台设置安装前缀 if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if(LINUX) set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "${CMAKE_BINARY_DIR}/AppDir/usr") @@ -95,10 +134,13 @@ if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) endif() endif() +# 若启用编译缓存功能 if(QGC_USE_CACHE) + # 在 Unix 系统上查找 ccache 工具 if(CMAKE_HOST_UNIX) find_program(CCACHE_PROGRAM ccache) if(CCACHE_PROGRAM) + # 设置 ccache 的相关参数 set(CCACHE_CMD CCACHE_BASEDIR=${CMAKE_BINARY_DIR} CCACHE_COMPRESSLEVEL=6 @@ -106,18 +148,22 @@ if(QGC_USE_CACHE) ${CCACHE_PROGRAM} ) message(STATUS "QGC: Using ccache cmd - ${CCACHE_CMD}") + # 设置 C 和 C++ 编译器及链接器的启动器为 ccache set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_CMD}") set(CMAKE_C_LINKER_LAUNCHER "${CCACHE_CMD}") set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_CMD}") set(CMAKE_CXX_LINKER_LAUNCHER "${CCACHE_CMD}") + # 若使用 Clang 编译器,添加编译选项禁用预编译头时间戳 if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Xclang -fno-pch-timestamp) endif() endif() + # 在 Windows 系统上查找 sccache 工具 elseif(CMAKE_HOST_WIN32) find_program(SCCACHE_PROGRAM sccache) if(SCCACHE_PROGRAM) message(STATUS "QGC: Using sccache ${SCCACHE_PROGRAM}") + # 设置 C 和 C++ 编译器的启动器为 sccache set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}") # set(CMAKE_C_LINKER_LAUNCHER "${SCCACHE_PROGRAM}") set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PROGRAM}") @@ -126,106 +172,136 @@ if(QGC_USE_CACHE) endif() endif() +# 若使用 MSVC 编译器,将调试信息格式设置为嵌入到可执行文件中 if(MSVC) set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) endif() -if(CMAKE_CROSSCOMPILING) - if(ANDROID) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) - endif() - - if(NOT IS_DIRECTORY ${QT_HOST_PATH}) - message(FATAL_ERROR "You need to set QT_HOST_PATH to cross compile Qt.") - endif() -endif() - +# # 若进行交叉编译 +# if(CMAKE_CROSSCOMPILING) +# # 若目标平台为 Android,设置查找库、头文件和包的路径模式为 BOTH +# if(ANDROID) +# set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) +# set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +# set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +# endif() + +# # 检查 QT_HOST_PATH 目录是否存在,若不存在则报错 +# if(NOT IS_DIRECTORY ${QT_HOST_PATH}) +# message(FATAL_ERROR "You need to set QT_HOST_PATH to cross compile Qt.") +# endif() +# endif() + +# 设置静态库的输出目录,包含构建配置类型(如 Debug、Release) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$/lib) +# 设置共享库的输出目录,包含构建配置类型 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$) +# 设置可执行文件的输出目录,包含构建配置类型 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$) -# https://cmake.org/cmake/help/latest/policy/CMP0168.html#policy:CMP0168 -if(POLICY CMP0168) - cmake_policy(SET CMP0168 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0168 NEW) +# # 处理 CMP0168 策略,该策略与 add_custom_command 命令的 DEPFILE 选项有关 +# # https://cmake.org/cmake/help/latest/policy/CMP0168.html#policy:CMP0168 +# if(POLICY CMP0168) +# cmake_policy(SET CMP0168 NEW) +# set(CMAKE_POLICY_DEFAULT_CMP0168 NEW) +# endif() + +# # 处理 CMP0075 策略,该策略与查找包时的版本比较有关 +# # https://cmake.org/cmake/help/latest/policy/CMP0075.html#policy:CMP0075 +# if(POLICY CMP0075) +# cmake_policy(SET CMP0075 NEW) +# set(CMAKE_POLICY_DEFAULT_CMP0075 NEW) +# endif() + + +if(POLICY CMP0079) + cmake_policy(SET CMP0079 NEW) # 允许跨目录链接 endif() -# https://cmake.org/cmake/help/latest/policy/CMP0075.html#policy:CMP0075 -if(POLICY CMP0075) - cmake_policy(SET CMP0075 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0075 NEW) +if(POLICY CMP0076) + cmake_policy(SET CMP0076 NEW) # 处理相对路径 endif() ####################################################### # Qt6 Configuration ####################################################### -# The values specified below are the ONLY supported configurations for this version of QGC. -# Change these to something else at your own risk. Anything other than what is specified +# The values specified below are the ONLY supported configurations for this version of QGC. +# Change these to something else at your own risk. Anything other than what is specified # here is unsupported so don't expect any help with problems. +# 根据不同条件设置 Qt 6 版本范围和 Android 最小 SDK 版本 if(QGC_ENABLE_HERELINK AND ANDROID) - set(QGC_QT_MINIMUM_VERSION "6.6.3" CACHE STRING "Minimum Supported Qt Version") - set(QGC_QT_MAXIMUM_VERSION "6.6.3" CACHE STRING "Maximum Supported Qt Version") - set(QGC_QT_ANDROID_MIN_SDK_VERSION "25" CACHE STRING "Android Min SDK Version") + # 若启用 Herelink 且目标平台为 Android,设置支持的 Qt 6 最小版本为 6.6.3 + set(QGC_QT_MINIMUM_VERSION "6.6.3" CACHE STRING "Minimum Supported Qt Version") + # 若启用 Herelink 且目标平台为 Android,设置支持的 Qt 6 最大版本为 6.6.3 + set(QGC_QT_MAXIMUM_VERSION "6.9.3" CACHE STRING "Maximum Supported Qt Version") + # 若启用 Herelink 且目标平台为 Android,设置 Android 最小 SDK 版本为 25 + set(QGC_QT_ANDROID_MIN_SDK_VERSION "25" CACHE STRING "Android Min SDK Version") else() - set(QGC_QT_MINIMUM_VERSION "6.8.3" CACHE STRING "Minimum Supported Qt Version") - set(QGC_QT_MAXIMUM_VERSION "6.8.3" CACHE STRING "Maximum Supported Qt Version") - set(QGC_QT_ANDROID_MIN_SDK_VERSION "28" CACHE STRING "Android Min SDK Version") + # 其他情况,设置支持的 Qt 6 最小版本为 6.8.3 + set(QGC_QT_MINIMUM_VERSION "6.8.3" CACHE STRING "Minimum Supported Qt Version") + # 其他情况,设置支持的 Qt 6 最大版本为 6.9.3 + set(QGC_QT_MAXIMUM_VERSION "6.9.3" CACHE STRING "Maximum Supported Qt Version") + # 其他情况,设置 Android 最小 SDK 版本为 28 + set(QGC_QT_ANDROID_MIN_SDK_VERSION "28" CACHE STRING "Android Min SDK Version") endif() +# 查找符合指定版本范围的 Qt 6 库,若未找到则报错 find_package(Qt6 ${QGC_QT_MINIMUM_VERSION}...${QGC_QT_MAXIMUM_VERSION} REQUIRED COMPONENTS - Charts - Concurrent - Core - Core5Compat - Gui - LinguistTools - Location - Multimedia - Network - OpenGL - Positioning - Qml - QmlIntegration - Quick - QuickControls2 - QuickWidgets - Sensors - Sql - Svg - TextToSpeech - Widgets - Xml + Charts # Qt Charts 组件 + Concurrent # Qt Concurrent 组件 + Core # Qt Core 核心组件 + Core5Compat # Qt Core 5 兼容组件 + Gui # Qt GUI 组件 + LinguistTools # Qt 语言工具组件 + Location # Qt Location 组件 + Multimedia # Qt Multimedia 组件 + Network # Qt Network 组件 + OpenGL # Qt OpenGL 组件 + Positioning # Qt Positioning 组件 + Qml # Qt QML 组件 + QmlIntegration # Qt QML 集成组件 + Quick # Qt Quick 组件 + QuickControls2 # Qt Quick Controls 2 组件 + QuickWidgets # Qt Quick Widgets 组件 + Sensors # Qt Sensors 组件 + Sql # Qt SQL 组件 + Svg # Qt SVG 组件 + TextToSpeech # Qt TextToSpeech 组件 + Widgets # Qt Widgets 组件 + Xml # Qt XML 组件 OPTIONAL_COMPONENTS - Bluetooth - MultimediaQuickPrivate - Quick3D - SerialPort - Test + Bluetooth # Qt Bluetooth 组件 + MultimediaQuickPrivate # Qt Multimedia Quick 私有组件 + Quick3D # Qt Quick 3D 组件 + SerialPort # Qt SerialPort 组件 + Test # Qt Test 组件 ) +# 若目标平台为 Linux,查找 Qt 6 的 WaylandClient 组件 if(LINUX) find_package(Qt6 COMPONENTS WaylandClient) endif() -# Set extra standard project setup options for Qt 6.7.0 and above +# 为 Qt 6.7.0 及以上版本设置额外的标准项目设置选项 set(EXTRA_STANDARD_PROJECT_SETUP_OPTIONS) if(Qt6_VERSION VERSION_GREATER_EQUAL 6.7.0) + # 若 Qt 6 版本大于等于 6.7.0,添加国际化源语言为英语的选项 list(APPEND EXTRA_STANDARD_PROJECT_SETUP_OPTIONS I18N_SOURCE_LANGUAGE en) endif() +# 进行 Qt 标准项目设置 qt_standard_project_setup( - REQUIRES ${QGC_QT_MINIMUM_VERSION} - SUPPORTS_UP_TO ${QGC_QT_MAXIMUM_VERSION} - ${EXTRA_STANDARD_PROJECT_SETUP_OPTIONS} + REQUIRES ${QGC_QT_MINIMUM_VERSION} # 要求的最低 Qt 版本 + SUPPORTS_UP_TO ${QGC_QT_MAXIMUM_VERSION} # 支持的最高 Qt 版本 + ${EXTRA_STANDARD_PROJECT_SETUP_OPTIONS} # 额外的标准项目设置选项 ) +# 设置 Qt 策略为 NEW 模式 qt_policy( SET QTP0001 NEW SET QTP0002 NEW @@ -238,19 +314,24 @@ qt_policy( # QGroundControl Options ####################################################### +# 根据构建类型设置编译定义 if(CMAKE_BUILD_TYPE STREQUAL "Release") + # 若为 Release 构建类型,添加 NDEBUG 和 QT_NO_DEBUG 编译定义 add_compile_definitions( NDEBUG QT_NO_DEBUG ) else() + # 其他构建类型,添加禁用弃用功能、启用严格模式的编译定义 add_compile_definitions( QT_DISABLE_DEPRECATED_UP_TO=0x060800 QT_ENABLE_STRICT_MODE_UP_TO=0x060800 + # 若 QGC_DEBUG_QML 为真,添加 QT_QML_DEBUG 编译定义 $<$:QT_QML_DEBUG> ) endif() +# 若不进行 QGC 测试构建,强制关闭 CMake 的测试构建功能 if(NOT QGC_BUILD_TESTING) set(BUILD_TESTING OFF CACHE INTERNAL "" FORCE) endif() @@ -259,6 +340,7 @@ endif() # Custom Build Configuration ####################################################### +# 若启用自定义构建,添加 custom 子目录到构建过程 if(QGC_CUSTOM_BUILD) add_subdirectory(custom) endif() @@ -267,26 +349,30 @@ endif() # QGroundControl Resources ####################################################### -# Note: Adding Resources to Library instead requires using Q_INIT_RESOURCE(qgcresources) +# 注意:若将资源添加到库中,需要使用 Q_INIT_RESOURCE(qgcresources) +# 向 QGC_RESOURCES 列表添加资源文件 list(APPEND QGC_RESOURCES - ${CMAKE_SOURCE_DIR}/qgcimages.qrc - ${CMAKE_SOURCE_DIR}/qgcresources.qrc - ${CMAKE_SOURCE_DIR}/qgroundcontrol.qrc + ${CMAKE_SOURCE_DIR}/qgcimages.qrc # 图像资源文件 + ${CMAKE_SOURCE_DIR}/qgcresources.qrc # 通用资源文件 + ${CMAKE_SOURCE_DIR}/qgroundcontrol.qrc # 主程序资源文件 ) +# 继续向 QGC_RESOURCES 列表添加资源文件 list(APPEND QGC_RESOURCES - ${CMAKE_SOURCE_DIR}/resources/InstrumentValueIcons/InstrumentValueIcons.qrc - ${CMAKE_SOURCE_DIR}/src/FirmwarePlugin/APM/APMResources.qrc - ${CMAKE_SOURCE_DIR}/src/FirmwarePlugin/PX4/PX4Resources.qrc + ${CMAKE_SOURCE_DIR}/resources/InstrumentValueIcons/InstrumentValueIcons.qrc # 仪器值图标资源文件 + ${CMAKE_SOURCE_DIR}/src/FirmwarePlugin/APM/APMResources.qrc # APM 固件插件资源文件 + ${CMAKE_SOURCE_DIR}/src/FirmwarePlugin/PX4/PX4Resources.qrc # PX4 固件插件资源文件 ) +# 根据 QGC_UTM_ADAPTER 变量的值添加不同的 UTM 服务提供商资源文件 if(QGC_UTM_ADAPTER) list(APPEND QGC_RESOURCES ${CMAKE_SOURCE_DIR}/src/UTMSP/utmsp.qrc) else() list(APPEND QGC_RESOURCES ${CMAKE_SOURCE_DIR}/src/UTMSP/dummy/utmsp_dummy.qrc) endif() +# 若进行 QGC 测试构建,添加单元测试资源文件 if(QGC_BUILD_TESTING) list(APPEND QGC_RESOURCES ${CMAKE_SOURCE_DIR}/test/UnitTest.qrc) endif() @@ -502,47 +588,47 @@ target_precompile_headers(${CMAKE_PROJECT_NAME} ) add_subdirectory(src) -if(QGC_BUILD_TESTING) - add_subdirectory(test) -endif() - -if(QGC_CUSTOM_BUILD) - target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_SOURCES}) - target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_LIBRARIES}) - target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_DIRECTORIES}) -endif() - -file(GLOB TS_SOURCES ${CMAKE_SOURCE_DIR}/translations/qgc_*.ts) -set_source_files_properties(${TS_SOURCES} PROPERTIES OUTPUT_LOCATION "${CMAKE_BINARY_DIR}/i18n") -qt_add_translations(${CMAKE_PROJECT_NAME} - # TS_FILE_BASE ${CMAKE_PROJECT_NAME} - # TS_FILE_DIR ${CMAKE_SOURCE_DIR}/translations/ - TS_FILES ${TS_SOURCES} - # TS_FILES_OUTPUT_VARIABLE - # QM_FILES_OUTPUT_VARIABLE - # TARGETS ${CMAKE_PROJECT_NAME} - # SOURCE_TARGETS ${CMAKE_PROJECT_NAME} - RESOURCE_PREFIX "/" - LUPDATE_OPTIONS -no-obsolete -) - -set_source_files_properties(resources/qtquickcontrols2.conf PROPERTIES QT_RESOURCE_ALIAS qtquickcontrols2.conf) -set_source_files_properties(${SDL_GAMECONTROLLERDB_PATH} PROPERTIES QT_RESOURCE_ALIAS gamecontrollerdb.txt) -qt_add_resources(${CMAKE_PROJECT_NAME} "qgcresources_cmake" - PREFIX "/" - FILES - resources/qtquickcontrols2.conf - ${SDL_GAMECONTROLLERDB_PATH} -) - -# cmake_print_variables(QT_ALL_PLUGIN_TYPES_FOUND_VIA_FIND_PACKAGE) -qt_import_plugins(${CMAKE_PROJECT_NAME} - INCLUDE Qt6::QSvgPlugin - EXCLUDE_BY_TYPE geoservices - INCLUDE_BY_TYPE sqldrivers Qt6::QSQLiteDriverPlugin - # INCLUDE_BY_TYPE styles Qt6::qtquickcontrols2basicstyleplugin Qt6::qtquickcontrols2basicstyleimplplugin -) - -include(Install) - -include(PrintSummary) +# if(QGC_BUILD_TESTING) +# add_subdirectory(test) +# endif() + +# if(QGC_CUSTOM_BUILD) +# target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_SOURCES}) +# target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_LIBRARIES}) +# target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CUSTOM_DIRECTORIES}) +# endif() + +# file(GLOB TS_SOURCES ${CMAKE_SOURCE_DIR}/translations/qgc_*.ts) +# set_source_files_properties(${TS_SOURCES} PROPERTIES OUTPUT_LOCATION "${CMAKE_BINARY_DIR}/i18n") +# qt_add_translations(${CMAKE_PROJECT_NAME} +# # TS_FILE_BASE ${CMAKE_PROJECT_NAME} +# # TS_FILE_DIR ${CMAKE_SOURCE_DIR}/translations/ +# TS_FILES ${TS_SOURCES} +# # TS_FILES_OUTPUT_VARIABLE +# # QM_FILES_OUTPUT_VARIABLE +# # TARGETS ${CMAKE_PROJECT_NAME} +# # SOURCE_TARGETS ${CMAKE_PROJECT_NAME} +# RESOURCE_PREFIX "/" +# LUPDATE_OPTIONS -no-obsolete +# ) + +# set_source_files_properties(resources/qtquickcontrols2.conf PROPERTIES QT_RESOURCE_ALIAS qtquickcontrols2.conf) +# set_source_files_properties(${SDL_GAMECONTROLLERDB_PATH} PROPERTIES QT_RESOURCE_ALIAS gamecontrollerdb.txt) +# qt_add_resources(${CMAKE_PROJECT_NAME} "qgcresources_cmake" +# PREFIX "/" +# FILES +# resources/qtquickcontrols2.conf +# ${SDL_GAMECONTROLLERDB_PATH} +# ) + +# # cmake_print_variables(QT_ALL_PLUGIN_TYPES_FOUND_VIA_FIND_PACKAGE) +# qt_import_plugins(${CMAKE_PROJECT_NAME} +# INCLUDE Qt6::QSvgPlugin +# EXCLUDE_BY_TYPE geoservices +# INCLUDE_BY_TYPE sqldrivers Qt6::QSQLiteDriverPlugin +# # INCLUDE_BY_TYPE styles Qt6::qtquickcontrols2basicstyleplugin Qt6::qtquickcontrols2basicstyleimplplugin +# ) + +# include(Install) + +# include(PrintSummary) diff --git a/cmake/Git.cmake b/cmake/Git.cmake index 2372fbf3b10999f8032132085640f3575c72d739..b9bb6dc9f7b38b479978fce14a26f2592fa7b11a 100644 --- a/cmake/Git.cmake +++ b/cmake/Git.cmake @@ -1,74 +1,98 @@ +# 尝试在系统中查找 Git 工具 find_package(Git) +# 检查是否找到了 Git 工具,并且项目根目录下存在 .git 文件夹(即项目是一个 Git 仓库) if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") + # 定义一个 CMake 选项,用于控制在构建过程中是否检查子模块,默认关闭 option(GIT_SUBMODULE "Check submodules during build" OFF) if(GIT_SUBMODULE) + # 输出状态信息,提示正在更新子模块 message(STATUS "Submodule update") + # 执行 Git 命令来更新子模块,递归初始化所有子模块 execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - RESULT_VARIABLE GIT_SUBMODULE_RESULT - OUTPUT_VARIABLE GIT_SUBMODULE_OUTPUT - ERROR_VARIABLE GIT_SUBMODULE_ERROR - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" # 在项目根目录下执行命令 + RESULT_VARIABLE GIT_SUBMODULE_RESULT # 存储命令执行结果(0 表示成功,非 0 表示失败) + OUTPUT_VARIABLE GIT_SUBMODULE_OUTPUT # 存储命令的标准输出 + ERROR_VARIABLE GIT_SUBMODULE_ERROR # 存储命令的错误输出 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) + # 检查子模块更新命令是否执行失败 if(NOT GIT_SUBMODULE_RESULT EQUAL 0) + # 打印子模块更新的结果、标准输出和错误输出,方便调试 cmake_print_variables(GIT_SUBMODULE_RESULT GIT_SUBMODULE_OUTPUT GIT_SUBMODULE_ERROR) + # 输出致命错误信息,终止 CMake 配置过程 message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMODULE_RESULT}, please checkout submodules") endif() endif() endif() +# 包含 CMakePrintHelpers 模块,以便使用 cmake_print_variables 命令 include(CMakePrintHelpers) +# 执行 Git 命令,获取当前所在的 Git 分支名称 execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref @ - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在项目根目录下执行命令 + OUTPUT_VARIABLE QGC_GIT_BRANCH # 存储命令的标准输出到 QGC_GIT_BRANCH 变量 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) -# cmake_print_variables(QGC_GIT_BRANCH) +# 取消注释下面这行可以打印当前 Git 分支名称,用于调试 +cmake_print_variables(QGC_GIT_BRANCH) +# 执行 Git 命令,获取当前提交的短哈希值 execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short @ - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_GIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在项目根目录下执行命令 + OUTPUT_VARIABLE QGC_GIT_HASH # 存储命令的标准输出到 QGC_GIT_HASH 变量 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) -# cmake_print_variables(QGC_GIT_HASH) +# 取消注释下面这行可以打印当前提交的短哈希值,用于调试 +cmake_print_variables(QGC_GIT_HASH) +# 执行 Git 命令,获取包含标签信息的版本字符串 execute_process( COMMAND ${GIT_EXECUTABLE} describe --always --tags - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_VERSION_STR - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在项目根目录下执行命令 + OUTPUT_VARIABLE QGC_APP_VERSION_STR # 存储命令的标准输出到 QGC_APP_VERSION_STR 变量 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) -# cmake_print_variables(QGC_APP_VERSION_STR) +# 取消注释下面这行可以打印包含标签信息的版本字符串,用于调试 +cmake_print_variables(QGC_APP_VERSION_STR) +# 执行 Git 命令,获取最近的标签名称 execute_process( COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=0 - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在项目根目录下执行命令 + OUTPUT_VARIABLE QGC_APP_VERSION # 存储命令的标准输出到 QGC_APP_VERSION 变量 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) -# cmake_print_variables(QGC_APP_VERSION) +# 取消注释下面这行可以打印最近的标签名称,用于调试 +cmake_print_variables(QGC_APP_VERSION) +# 执行 Git 命令,获取最近标签对应的提交日期 execute_process( COMMAND ${GIT_EXECUTABLE} log -1 --format=%aI ${QGC_APP_VERSION} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE QGC_APP_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在项目根目录下执行命令 + OUTPUT_VARIABLE QGC_APP_DATE # 存储命令的标准输出到 QGC_APP_DATE 变量 + OUTPUT_STRIP_TRAILING_WHITESPACE # 去除输出末尾的空白字符 ) -# cmake_print_variables(QGC_APP_DATE) +# 取消注释下面这行可以打印最近标签对应的提交日期,用于调试 +cmake_print_variables(QGC_APP_DATE) +# 检查版本号是否以 'v' 开头 string(FIND ${QGC_APP_VERSION} "v" QGC_APP_VERSION_VALID) if(QGC_APP_VERSION_VALID GREATER -1) + # 如果以 'v' 开头,将 'v' 从版本号中移除 string(REPLACE "v" "" QGC_APP_VERSION ${QGC_APP_VERSION}) else() + # 如果不以 'v' 开头,将版本号设置为默认值 0.0.0 set(QGC_APP_VERSION "0.0.0") endif() +# 使用正则表达式从版本号中提取主版本号、次版本号和修订号 string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" QGC_APP_VERSION_MATCH ${QGC_APP_VERSION}) -set(QGC_APP_VERSION_MAJOR ${CMAKE_MATCH_1}) -set(QGC_APP_VERSION_MINOR ${CMAKE_MATCH_2}) -set(QGC_APP_VERSION_PATCH ${CMAKE_MATCH_3}) -# cmake_print_variables(QGC_APP_VERSION QGC_APP_VERSION_MAJOR QGC_APP_VERSION_MINOR QGC_APP_VERSION_PATCH) +set(QGC_APP_VERSION_MAJOR ${CMAKE_MATCH_1}) # 主版本号 +set(QGC_APP_VERSION_MINOR ${CMAKE_MATCH_2}) # 次版本号 +set(QGC_APP_VERSION_PATCH ${CMAKE_MATCH_3}) # 修订号 +# 取消注释下面这行可以打印版本号及其各部分,用于调试 +cmake_print_variables(QGC_APP_VERSION QGC_APP_VERSION_MAJOR QGC_APP_VERSION_MINOR QGC_APP_VERSION_PATCH) diff --git a/cmake/Prechecks.cmake b/cmake/Prechecks.cmake index 7f6b27239211a3d71d9987fd676139890c9da748..603808a9826795469bd0ab35656788b591f9b7e2 100644 --- a/cmake/Prechecks.cmake +++ b/cmake/Prechecks.cmake @@ -1,5 +1,7 @@ +# 强制要求 Qt6 主版本 set(QT_DEFAULT_MAJOR_VERSION 6) +# 多路径查找 qmake6 可执行文件 find_program(QMAKE_EXECUTABLE NAMES qmake6 HINTS ${QT_HOST_PATH} ${QT_ROOT_DIR} ${QTDIR} @@ -7,28 +9,17 @@ find_program(QMAKE_EXECUTABLE PATH_SUFFIXES bin ) -if(NOT QMAKE_EXECUTABLE) - message(FATAL_ERROR "qmake6 not found. Please set QT_ROOT_DIR or QTDIR correctly.") -endif() - +# 版本查询与验证流程 execute_process( COMMAND "${QMAKE_EXECUTABLE}" -query QT_VERSION OUTPUT_VARIABLE QT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE _qmake_query_res ) -if(_qmake_query_res) - message(FATAL_ERROR "Failed to run ${QMAKE_EXECUTABLE} -query QT_VERSION") -endif() +# 版本范围检查 if(QT_VERSION VERSION_LESS "${QGC_QT_MINIMUM_VERSION}") - message(FATAL_ERROR - "Qt version too old: need ≥ ${QGC_QT_MINIMUM_VERSION}, " - "found ${QT_VERSION}" - ) + # message(FATAL_ERROR "Qt版本过低") elseif(QT_VERSION VERSION_GREATER "${QGC_QT_MAXIMUM_VERSION}") - message(FATAL_ERROR - "Qt version too new: need ≤ ${QGC_QT_MAXIMUM_VERSION}, " - "found ${QT_VERSION}" - ) + # message(FATAL_ERROR "Qt版本过高") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f1cc75a78176ef7b29ffe445033e5e8c821bb02..3d50d98a1e9a07889446da47e1474e7d3d20a2f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,22 +4,22 @@ # IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} # ) -target_sources(${CMAKE_PROJECT_NAME} - PRIVATE - main.cc - CmdLineOptParser.cc - CmdLineOptParser.h - QGCApplication.cc - QGCApplication.h -) +# target_sources(${CMAKE_PROJECT_NAME} +# PRIVATE +# main.cc +# CmdLineOptParser.cc +# CmdLineOptParser.h +# QGCApplication.cc +# QGCApplication.h +# ) -if(NOT ANDROID AND NOT IOS) - target_sources(${CMAKE_PROJECT_NAME} - PRIVATE - RunGuard.cc - RunGuard.h - ) -endif() +# if(NOT ANDROID AND NOT IOS) +# target_sources(${CMAKE_PROJECT_NAME} +# PRIVATE +# RunGuard.cc +# RunGuard.h +# ) +# endif() target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE @@ -82,34 +82,34 @@ endif() target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -add_subdirectory(ADSB) -add_subdirectory(AnalyzeView) -add_subdirectory(Android) -add_subdirectory(API) -add_subdirectory(AutoPilotPlugins) -add_subdirectory(Camera) -add_subdirectory(Comms) -add_subdirectory(FactSystem) -add_subdirectory(FirmwarePlugin) -add_subdirectory(FirstRunPromptDialogs) -add_subdirectory(FlightDisplay) -add_subdirectory(FlightMap) -add_subdirectory(FollowMe) -add_subdirectory(Gimbal) -add_subdirectory(GPS) +# add_subdirectory(ADSB) +# add_subdirectory(AnalyzeView) +# add_subdirectory(Android) +# add_subdirectory(API) +# add_subdirectory(AutoPilotPlugins) +# add_subdirectory(Camera) +# add_subdirectory(Comms) +# add_subdirectory(FactSystem) +# add_subdirectory(FirmwarePlugin) +# add_subdirectory(FirstRunPromptDialogs) +# add_subdirectory(FlightDisplay) +# add_subdirectory(FlightMap) +# add_subdirectory(FollowMe) +# add_subdirectory(Gimbal) +# add_subdirectory(GPS) add_subdirectory(Joystick) add_subdirectory(MAVLink) -add_subdirectory(MissionManager) -add_subdirectory(PlanView) -add_subdirectory(PositionManager) -add_subdirectory(QmlControls) -add_subdirectory(Settings) -add_subdirectory(Terrain) -add_subdirectory(UI) -add_subdirectory(Utilities) -add_subdirectory(UTMSP) -add_subdirectory(Vehicle) -add_subdirectory(VideoManager) -add_subdirectory(Viewer3D) +# add_subdirectory(MissionManager) +# add_subdirectory(PlanView) +# add_subdirectory(PositionManager) +# add_subdirectory(QmlControls) +# add_subdirectory(Settings) +# add_subdirectory(Terrain) +# add_subdirectory(UI) +# add_subdirectory(Utilities) +# add_subdirectory(UTMSP) +# add_subdirectory(Vehicle) +# add_subdirectory(VideoManager) +# add_subdirectory(Viewer3D) -add_subdirectory(QtLocationPlugin) +# add_subdirectory(QtLocationPlugin) diff --git a/src/FactSystem/Fact.h b/src/FactSystem/Fact.h index a3e0962318e0c4b4601f7cdc4ed721d1cf7b6495..aec5e32f20d6de74d1d38c06ca433722e4dc0450 100644 --- a/src/FactSystem/Fact.h +++ b/src/FactSystem/Fact.h @@ -7,23 +7,33 @@ * ****************************************************************************/ +// 确保头文件只被编译一次,防止重复包含 #pragma once +// 包含 Qt 核心库的头文件 #include #include #include #include +// 包含自定义的 Fact 元数据头文件 #include "FactMetaData.h" +// 前向声明 FactValueSliderListModel 类 class FactValueSliderListModel; +// 声明日志记录类别 Q_DECLARE_LOGGING_CATEGORY(FactLog) -/// A Fact is used to hold a single value within the system. +/// @brief Fact 类用于在系统中存储单个值。 +/// +/// Fact 类继承自 QObject,使用 Qt 的属性系统来管理值及其相关元数据。 +/// 它提供了对值的读取、写入、验证和转换等功能,同时支持信号和槽机制, +/// 当值发生变化时可以发出相应的信号。 class Fact : public QObject { Q_OBJECT + // 定义 Qt 属性,可通过属性系统访问和监听变化 Q_PROPERTY(int componentId READ componentId CONSTANT) Q_PROPERTY(QStringList bitmaskStrings READ bitmaskStrings NOTIFY bitmaskStringsChanged) Q_PROPERTY(QVariantList bitmaskValues READ bitmaskValues NOTIFY bitmaskValuesChanged) @@ -64,142 +74,296 @@ class Fact : public QObject Q_PROPERTY(bool volatileValue READ volatileValue CONSTANT) public: + /// @brief 默认构造函数 + /// @param parent 父对象,默认为 nullptr explicit Fact(QObject *parent = nullptr); + /// @brief 构造函数,使用组件 ID、名称和类型初始化 Fact + /// @param componentId 组件 ID + /// @param name Fact 的名称 + /// @param type Fact 的值类型 + /// @param parent 父对象,默认为 nullptr explicit Fact(int componentId, const QString &name, FactMetaData::ValueType_t type, QObject *parent = nullptr); + /// @brief 拷贝构造函数 + /// @param other 要拷贝的 Fact 对象 + /// @param parent 父对象,默认为 nullptr explicit Fact(const Fact &other, QObject *parent = nullptr); - /// Creates a Fact using the name and type from metaData. Also calls QGCCorePlugin::adjustSettingsMetaData allowing - /// custom builds to override the metadata. + /// @brief 构造函数,使用设置组和元数据初始化 Fact + /// @param settingsGroup 设置组名称 + /// @param metaData Fact 的元数据 + /// @param parent 父对象,默认为 nullptr + /// @note 会调用 QGCCorePlugin::adjustSettingsMetaData 允许自定义构建覆盖元数据 explicit Fact(const QString &settingsGroup, FactMetaData *metaData, QObject *parent = nullptr); + /// @brief 析构函数 virtual ~Fact(); + /// @brief 赋值运算符重载 + /// @param other 要赋值的 Fact 对象 + /// @return 赋值后的 Fact 对象引用 const Fact &operator=(const Fact &other); - /// Convert and validate value - /// @param cookedValue: Value to convert and validate - /// @param convertOnly true: validate type conversion only, false: validate against meta data as well + /// @brief 转换并验证值 + /// @param cookedValue 要转换和验证的值 + /// @param convertOnly true: 仅验证类型转换,false: 同时根据元数据验证 + /// @return 验证结果字符串 Q_INVOKABLE QString validate(const QString &cookedValue, bool convertOnly); - /// Convert and clamp value + /// @brief 转换并钳制值 + /// @param cookedValue 要转换和钳制的值 + /// @return 钳制后的 QVariant 值 Q_INVOKABLE QVariant clamp(const QString &cookedValue); - QVariant cookedValue() const; /// Value after translation - QVariant rawValue() const { return _rawValue; } /// value prior to translation, careful + /// @brief 获取转换后的值 + /// @return 转换后的值 + QVariant cookedValue() const; + /// @brief 获取原始值 + /// @return 原始值 + QVariant rawValue() const { return _rawValue; } + /// @brief 获取组件 ID + /// @return 组件 ID int componentId() const { return _componentId; } + /// @brief 获取小数位数 + /// @return 小数位数 int decimalPlaces() const; + /// @brief 获取原始默认值 + /// @return 原始默认值 QVariant rawDefaultValue() const; + /// @brief 获取转换后的默认值 + /// @return 转换后的默认值 QVariant cookedDefaultValue() const; + /// @brief 判断默认值是否可用 + /// @return true: 可用,false: 不可用 bool defaultValueAvailable() const; + /// @brief 获取转换后的默认值字符串 + /// @return 转换后的默认值字符串 QString cookedDefaultValueString() const; + /// @brief 获取位掩码字符串列表 + /// @return 位掩码字符串列表 QStringList bitmaskStrings() const; + /// @brief 获取位掩码值列表 + /// @return 位掩码值列表 QVariantList bitmaskValues() const; - /// Provide a list of selected strings based on the fact value with the bitmaskString/bitmaskValues map + /// @brief 根据 Fact 值和位掩码字符串/值映射提供选中的字符串列表 + /// @return 选中的字符串列表 QStringList selectedBitmaskStrings() const; - int enumIndex(); // This is not const, since an unknown value can modify the enum lists + /// @brief 获取枚举索引 + /// @note 非 const 函数,因为未知值可能会修改枚举列表 + /// @return 枚举索引 + int enumIndex(); + /// @brief 获取枚举字符串列表 + /// @return 枚举字符串列表 QStringList enumStrings() const; - QString enumStringValue(); // This is not const, since an unknown value can modify the enum lists + /// @brief 获取枚举字符串值 + /// @note 非 const 函数,因为未知值可能会修改枚举列表 + /// @return 枚举字符串值 + QString enumStringValue(); + /// @brief 获取枚举值列表 + /// @return 枚举值列表 QVariantList enumValues() const; + /// @brief 获取类别 + /// @return 类别字符串 QString category() const; + /// @brief 获取组名 + /// @return 组名字符串 QString group() const; + /// @brief 获取长描述 + /// @return 长描述字符串 QString longDescription() const; + /// @brief 获取原始最大值 + /// @return 原始最大值 QVariant rawMax() const; + /// @brief 获取转换后的最大值 + /// @return 转换后的最大值 QVariant cookedMax() const; + /// @brief 获取转换后的最大值字符串 + /// @return 转换后的最大值字符串 QString cookedMaxString() const; + /// @brief 判断最大值是否为类型默认值 + /// @return true: 是,false: 否 bool maxIsDefaultForType() const; + /// @brief 获取原始最小值 + /// @return 原始最小值 QVariant rawMin() const; + /// @brief 获取转换后的最小值 + /// @return 转换后的最小值 QVariant cookedMin() const; + /// @brief 获取转换后的最小值字符串 + /// @return 转换后的最小值字符串 QString cookedMinString() const; + /// @brief 判断最小值是否为类型默认值 + /// @return true: 是,false: 否 bool minIsDefaultForType() const; + /// @brief 获取名称 + /// @return 名称字符串 QString name() const { return _name; } + /// @brief 获取短描述 + /// @return 短描述字符串 QString shortDescription() const; + /// @brief 获取值类型 + /// @return 值类型枚举 FactMetaData::ValueType_t type() const { return _type; } + /// @brief 获取转换后的单位 + /// @return 转换后的单位字符串 QString cookedUnits() const; + /// @brief 获取原始单位 + /// @return 原始单位字符串 QString rawUnits() const; + /// @brief 获取原始值字符串 + /// @return 原始值字符串 QString rawValueString() const; + /// @brief 获取转换后的值字符串 + /// @return 转换后的值字符串 QString cookedValueString() const; + /// @brief 判断当前值是否等于默认值 + /// @return true: 相等,false: 不相等 bool valueEqualsDefault() const; + /// @brief 判断是否需要重启载具 + /// @return true: 需要,false: 不需要 bool vehicleRebootRequired() const; + /// @brief 判断是否需要重启 QGroundControl + /// @return true: 需要,false: 不需要 bool qgcRebootRequired() const; - QString enumOrValueString(); // This is not const, since an unknown value can modify the enum lists + /// @brief 获取枚举或值字符串 + /// @note 非 const 函数,因为未知值可能会修改枚举列表 + /// @return 枚举或值字符串 + QString enumOrValueString(); + /// @brief 获取原始增量值 + /// @return 原始增量值 double rawIncrement() const; + /// @brief 获取转换后的增量值 + /// @return 转换后的增量值 double cookedIncrement() const; + /// @brief 判断值类型是否为字符串 + /// @return true: 是,false: 否 bool typeIsString() const { return (type() == FactMetaData::valueTypeString); } + /// @brief 判断值类型是否为布尔值 + /// @return true: 是,false: 否 bool typeIsBool() const { return (type() == FactMetaData::valueTypeBool); } + /// @brief 判断是否有控件 + /// @return true: 有,false: 没有 bool hasControl() const; + /// @brief 判断是否只读 + /// @return true: 只读,false: 可读写 bool readOnly() const; + /// @brief 判断是否只写 + /// @return true: 只写,false: 可读写 bool writeOnly() const; + /// @brief 判断值是否为易失性 + /// @return true: 是,false: 否 bool volatileValue() const; + /// @brief 获取值滑块模型 + /// @return FactValueSliderListModel 指针 Q_INVOKABLE FactValueSliderListModel *valueSliderModel(); - /// Returns the values as a string with full 18 digit precision if float/double. + /// @brief 以 18 位全精度返回值的字符串表示(如果是浮点数) + /// @return 全精度值字符串 QString rawValueStringFullPrecision() const; + /// @brief 设置原始值 + /// @param value 要设置的原始值 void setRawValue(const QVariant &value); + /// @brief 设置转换后的值 + /// @param value 要设置的转换后的值 void setCookedValue(const QVariant &value); + /// @brief 设置枚举索引 + /// @param index 要设置的枚举索引 void setEnumIndex(int index); + /// @brief 设置枚举字符串值 + /// @param value 要设置的枚举字符串值 void setEnumStringValue(const QString &value); + /// @brief 获取值的索引 + /// @param value 要查找的字符串值 + /// @return 值的索引 int valueIndex(const QString &value) const; - // The following methods allow you to defer sending of the valueChanged signals in order to implement - // rate limited signalling for ui performance. Used by FactGroup for example. + // 以下方法允许你延迟发送值改变信号,以实现 UI 性能的限速信号。例如由 FactGroup 使用。 + /// @brief 设置是否发送值改变信号 + /// @param sendValueChangedSignals true: 发送,false: 不发送 void setSendValueChangedSignals (bool sendValueChangedSignals); + /// @brief 获取是否发送值改变信号 + /// @return true: 发送,false: 不发送 bool sendValueChangedSignals () const { return _sendValueChangedSignals; } + /// @brief 判断是否有延迟的值改变信号 + /// @return true: 有,false: 没有 bool deferredValueChangeSignal() const { return _deferredValueChangeSignal; } + /// @brief 清除延迟的值改变信号标记 void clearDeferredValueChangeSignal() { _deferredValueChangeSignal = false; } + /// @brief 发送延迟的值改变信号 void sendDeferredValueChangedSignal(); - /// Sets and sends new value to vehicle even if value is the same + /// @brief 强制设置原始值并发送新值到载具,即使值相同 + /// @param value 要设置的原始值 void forceSetRawValue(const QVariant &value); - /// Sets the meta data associated with the Fact. - /// @param metaData FactMetaData for Fact - /// @param setDefaultFromMetaData true: set the fact value to the default specified in the meta data + /// @brief 设置与 Fact 关联的元数据 + /// @param metaData Fact 的元数据 + /// @param setDefaultFromMetaData true: 将 Fact 值设置为元数据中指定的默认值 void setMetaData(FactMetaData *metaData, bool setDefaultFromMetaData = false); + /// @brief 获取元数据指针 + /// @return FactMetaData 指针 FactMetaData *metaData() { return _metaData; } - /// Value coming from Vehicle. This does NOT send a _containerRawValueChanged signal. + /// @brief 设置来自载具的值,不发送 _containerRawValueChanged 信号 + /// @param value 要设置的值 void containerSetRawValue(const QVariant &value); - /// Generally you should not change the name of a fact. But if you know what you are doing, you can. + /// @brief 一般情况下不应该更改 Fact 的名称,但如果你知道自己在做什么,可以调用此方法 + /// @param name 要设置的新名称 void setName(const QString &name) { _name = name; } - /// Generally this is done during parsing. But if you know what you are doing, you can. + /// @brief 一般情况下在解析时设置枚举信息,但如果你知道自己在做什么,可以调用此方法 + /// @param strings 枚举字符串列表 + /// @param values 枚举值列表 void setEnumInfo(const QStringList &strings, const QVariantList &values); signals: + /// @brief 位掩码字符串列表改变时发出的信号 void bitmaskStringsChanged(); + /// @brief 位掩码值列表改变时发出的信号 void bitmaskValuesChanged(); + /// @brief 枚举信息改变时发出的信号 void enumsChanged(); + /// @brief 是否发送值改变信号的状态改变时发出的信号 void sendValueChangedSignalsChanged(bool sendValueChangedSignals); - /// This signal is only meant for use by the QT property system. It should not be connected to by client code. + /// @brief 此信号仅用于 QT 属性系统,客户端代码不应连接此信号 void valueChanged(const QVariant &value); + /// @brief 原始值改变时发出的信号 void rawValueChanged(const QVariant &value); - /// Signalled when the param write ack comes back from the vehicle + /// @brief 当参数写入确认从载具返回时发出的信号 void vehicleUpdated(const QVariant &value); - /// This signal is meant for use by Fact container implementations. Used to send changed values to vehicle. + /// @brief 此信号用于 Fact 容器实现,用于将改变的值发送到载具 void containerRawValueChanged(const QVariant &value); protected: + /// @brief 将 QVariant 转换为字符串 + /// @param variant 要转换的 QVariant 值 + /// @param decimalPlaces 小数位数 + /// @return 转换后的字符串 QString _variantToString(const QVariant &variant, int decimalPlaces) const; + /// @brief 发送值改变信号 + /// @param value 改变后的值 void _sendValueChangedSignal(const QVariant &value); - QString _name; - int _componentId = -1; - QVariant _rawValue = 0; - FactMetaData::ValueType_t _type = FactMetaData::valueTypeInt32; - FactMetaData *_metaData = nullptr; - bool _sendValueChangedSignals = true; - bool _deferredValueChangeSignal = false; - FactValueSliderListModel *_valueSliderModel = nullptr; + QString _name; // Fact 的名称 + int _componentId = -1; // 组件 ID + QVariant _rawValue = 0; // 原始值 + FactMetaData::ValueType_t _type = FactMetaData::valueTypeInt32; // 值类型 + FactMetaData *_metaData = nullptr; // 元数据指针 + bool _sendValueChangedSignals = true; // 是否发送值改变信号 + bool _deferredValueChangeSignal = false; // 是否有延迟的值改变信号 + FactValueSliderListModel *_valueSliderModel = nullptr; // 值滑块模型指针 + // 缺少元数据时的错误信息 static constexpr const char *kMissingMetadata = "Meta data pointer missing"; private slots: + /// @brief 检查是否需要重启的消息提示 void _checkForRebootMessaging(); private: + /// @brief 初始化函数 void _init(); }; diff --git a/src/FactSystem/FactControls/FactLabel.qml b/src/FactSystem/FactControls/FactLabel.qml index a4e9d628360521b0393b8934cd1ee49f8d99101f..c0cec79ce1da4348bf2e4174d5f11af04635d28d 100644 --- a/src/FactSystem/FactControls/FactLabel.qml +++ b/src/FactSystem/FactControls/FactLabel.qml @@ -1,13 +1,41 @@ +// 导入 Qt Quick 基础模块,提供基本的 UI 组件和功能 import QtQuick +// 导入 Qt Quick 控件模块,包含一系列标准的 UI 控件 import QtQuick.Controls +// 导入 QGroundControl 自定义的 FactSystem 模块,用于处理配置项相关逻辑 import QGroundControl.FactSystem +// 导入 QGroundControl 自定义的调色板模块,用于管理应用的颜色方案 import QGroundControl.Palette +// 导入 QGroundControl 自定义的控件模块,包含自定义的 UI 控件 import QGroundControl.Controls +/** + * @brief 自定义标签组件,用于显示 Fact 对象的值及其单位 + * + * 该组件继承自 QGCLabel,可显示 Fact 对象的当前值,并根据配置决定是否显示单位。 + */ QGCLabel { + /** + * @brief 是否显示单位的属性 + * + * 当该属性为 true 时,在显示 Fact 对象的值后会接着显示单位;为 false 时只显示值。 + * 默认值为 true。 + */ property bool showUnits: true + /** + * @brief Fact 对象属性 + * + * 用于存储要显示的 Fact 对象,默认创建一个空的 Fact 对象。 + * Fact 对象包含值、单位等信息。 + */ property Fact fact: Fact { } + /** + * @brief 标签显示的文本内容 + * + * 根据 Fact 对象的值和 showUnits 属性的值,决定显示的文本内容。 + * 如果 showUnits 为 true,则显示值和单位;否则只显示值。 + */ text: fact.valueString + (showUnits ? " " + fact.units : "") } diff --git a/src/FactSystem/SettingsFact.h b/src/FactSystem/SettingsFact.h index 313e3de7461fb02769ea9dd16be5cde735b51f89..0f376fe4ed7ea417b95358aacf8d7a303558825d 100644 --- a/src/FactSystem/SettingsFact.h +++ b/src/FactSystem/SettingsFact.h @@ -7,37 +7,62 @@ * ****************************************************************************/ +// 确保头文件只被编译一次,防止重复包含 #pragma once +// 包含 Qt 核心库的日志记录、对象和字符串相关头文件 #include #include #include +// 包含自定义的 Fact 类头文件 #include "Fact.h" +// 声明 SettingsFact 相关的日志记录类别 Q_DECLARE_LOGGING_CATEGORY(SettingsFactLog) -/// A SettingsFact is Fact which holds a QSettings value. +/// @brief SettingsFact 类继承自 Fact 类,用于存储 QSettings 中的值。 +/// +/// 该类扩展了 Fact 类的功能,使其能够与 QSettings 交互,将设置值持久化存储。 +/// 同时提供了控制该设置项是否可见的功能。 class SettingsFact : public Fact { Q_OBJECT + // 定义 Qt 属性,用于获取设置项的可见性,该属性为常量,初始化后不可变 Q_PROPERTY(bool visible MEMBER _visible CONSTANT) public: + /// @brief 默认构造函数 + /// @param parent 父对象,默认为 nullptr explicit SettingsFact(QObject *parent = nullptr); + /// @brief 构造函数,使用设置组、元数据和父对象初始化 SettingsFact + /// @param settingsGroup 设置组名称,用于在 QSettings 中分组存储 + /// @param metaData 设置项的元数据 + /// @param parent 父对象,默认为 nullptr explicit SettingsFact(const QString &settingsGroup, FactMetaData *metaData, QObject *parent = nullptr); + /// @brief 拷贝构造函数 + /// @param other 要拷贝的 SettingsFact 对象 + /// @param parent 父对象,默认为 nullptr explicit SettingsFact(const SettingsFact &other, QObject *parent = nullptr); + /// @brief 析构函数 ~SettingsFact(); + /// @brief 赋值运算符重载 + /// @param other 要赋值的 SettingsFact 对象 + /// @return 赋值后的 SettingsFact 对象引用 const SettingsFact &operator=(const SettingsFact &other); - // Must be called before any references to fact + /// @brief 设置设置项的可见性 + /// @note 必须在任何对该设置项的引用之前调用此函数 + /// @param visible true 表示可见,false 表示不可见 void setVisible(bool visible) { _visible = visible; } private slots: + /// @brief 原始值改变时的槽函数 + /// @param value 改变后的原始值 void _rawValueChanged(const QVariant &value); private: - QString _settingsGroup; - bool _visible = true; + QString _settingsGroup; // 设置组名称,用于在 QSettings 中标识存储位置 + bool _visible = true; // 设置项的可见性,默认为可见 }; diff --git a/src/Joystick/CMakeLists.txt b/src/Joystick/CMakeLists.txt index 941047bf2911c46e25d914b5a036e54b604d11a3..772f25a5ee81057bc3befd993b369a74901e521c 100644 --- a/src/Joystick/CMakeLists.txt +++ b/src/Joystick/CMakeLists.txt @@ -29,7 +29,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE QGC_SDL_JOYSTICK) CPMAddPackage( NAME sdl_gamecontrollerdb - GITHUB_REPOSITORY mdqinc/SDL_GameControllerDB + GIT_REPOSITORY "https://gitee.com/wxculinor/SDL_GameControllerDB.git" GIT_TAG master ) @@ -48,7 +48,7 @@ endif() CPMAddPackage( NAME SDL2 VERSION 2.32.4 - GITHUB_REPOSITORY libsdl-org/SDL + GIT_REPOSITORY "https://gitee.com/open-source-software_1/SDL.git" GIT_TAG release-2.32.4 OPTIONS "SDL2_DISABLE_INSTALL ON" diff --git a/src/Joystick/Joystick.cc b/src/Joystick/Joystick.cc index 23a35c1ea860f0eec9f5beae70c95e6c99374277..1e2d63e1aabffad80061ff20823d3002d6c50265 100644 --- a/src/Joystick/Joystick.cc +++ b/src/Joystick/Joystick.cc @@ -45,6 +45,7 @@ AssignableButtonAction::AssignableButtonAction(const QString &action_, bool canR /*===========================================================================*/ +// 初始化遥控器模式为 2 int Joystick::_transmitterMode = 2; Joystick::Joystick(const QString &name, int axisCount, int buttonCount, int hatCount, QObject *parent) @@ -73,9 +74,12 @@ Joystick::Joystick(const QString &name, int axisCount, int buttonCount, int hatC } _buildActionList(MultiVehicleManager::instance()->activeVehicle()); + // 更新遥控器模式设置键 _updateTXModeSettingsKey(MultiVehicleManager::instance()->activeVehicle()); + // 加载设置 _loadSettings(); + // 连接多飞行器管理器的信号到对应的槽函数 (void) connect(MultiVehicleManager::instance(), &MultiVehicleManager::activeVehicleChanged, this, &Joystick::_activeVehicleChanged); (void) connect(MultiVehicleManager::instance()->vehicles(), &QmlObjectListModel::countChanged, this, &Joystick::_vehicleCountChanged); } @@ -108,22 +112,25 @@ void Joystick::_setDefaultCalibration() settings.beginGroup(_settingsGroup); settings.beginGroup(_name); + // 从设置中获取校准状态 _calibrated = settings.value(_calibratedSettingsKey, false).toBool(); - // Only set default calibrations if we do not have a calibration for this gamecontroller + // 如果已经校准,则不设置默认校准参数 if (_calibrated) { return; } + // 为每个轴设置默认校准参数 for (int axis = 0; axis < _axisCount; axis++) { Joystick::Calibration_t calibration; _rgCalibration[axis] = calibration; } + // 设置特定轴的反转属性 _rgCalibration[1].reversed = true; _rgCalibration[3].reversed = true; - // Default TX Mode 2 axis assignments for gamecontrollers + // 默认 TX 模式 2 的轴分配 _rgFunctionAxis[rollFunction] = 2; _rgFunctionAxis[pitchFunction] = 3; _rgFunctionAxis[yawFunction] = 0; @@ -132,6 +139,7 @@ void Joystick::_setDefaultCalibration() _rgFunctionAxis[gimbalPitchFunction] = 4; _rgFunctionAxis[gimbalYawFunction] = 5; + // 设置其他默认参数 _exponential = 0; _accumulator = false; _deadband = false; @@ -141,6 +149,7 @@ void Joystick::_setDefaultCalibration() _calibrated = true; _circleCorrection = false; + // 保存设置 _saveSettings(); } @@ -168,12 +177,14 @@ void Joystick::_updateTXModeSettingsKey(Vehicle *activeVehicle) void Joystick::_activeVehicleChanged(Vehicle *activeVehicle) { + // 更新遥控器模式设置键 _updateTXModeSettingsKey(activeVehicle); if (activeVehicle) { QSettings settings; settings.beginGroup(_settingsGroup); + // 从设置中读取遥控器模式 const int mode = settings.value(_txModeSettingsKey, activeVehicle->firmwarePlugin()->defaultJoystickTXMode()).toInt(); setTXMode(mode); } @@ -182,7 +193,7 @@ void Joystick::_activeVehicleChanged(Vehicle *activeVehicle) void Joystick::_vehicleCountChanged(int count) { if (count == 0) { - // then the last vehicle has been deleted + // 最后一个飞行器被删除,停止游戏手柄线程 qCDebug(JoystickLog) << "Stopping joystick thread due to last active vehicle deletion"; this->stopPolling(); } @@ -193,16 +204,20 @@ void Joystick::_loadSettings() QSettings settings; settings.beginGroup(_settingsGroup); + // 获取当前激活的飞行器对象 Vehicle *const activeVehicle = MultiVehicleManager::instance()->activeVehicle(); if (_txModeSettingsKey && activeVehicle) { + // 从设置中读取遥控器模式 _transmitterMode = settings.value(_txModeSettingsKey, activeVehicle->firmwarePlugin()->defaultJoystickTXMode()).toInt(); } settings.beginGroup(_name); + // 标记设置是否有误 bool badSettings = false; bool convertOk; + // 从设置中读取各种参数 _calibrated = settings.value(_calibratedSettingsKey, false).toBool(); _exponential = settings.value(_exponentialSettingsKey, 0).toFloat(); _accumulator = settings.value(_accumulatorSettingsKey, false).toBool(); @@ -217,12 +232,14 @@ void Joystick::_loadSettings() qCDebug(JoystickLog) << Q_FUNC_INFO << "calibrated:txmode:throttlemode:exponential:deadband:badsettings" << _calibrated << _transmitterMode << _throttleMode << _exponential << _deadband << badSettings; + // 模板字符串,用于构建轴校准参数的键名 const QString minTpl("Axis%1Min"); const QString maxTpl("Axis%1Max"); const QString trimTpl("Axis%1Trim"); const QString revTpl("Axis%1Rev"); const QString deadbndTpl("Axis%1Deadbnd"); + // 读取每个轴的校准参数 for (int axis = 0; axis < _axisCount; axis++) { Calibration_t *const calibration = &_rgCalibration[axis]; @@ -243,7 +260,9 @@ void Joystick::_loadSettings() qCDebug(JoystickLog) << Q_FUNC_INFO << "axis:min:max:trim:reversed:deadband:badsettings" << axis << calibration->min << calibration->max << calibration->center << calibration->reversed << calibration->deadband << badSettings; } + // 有效轴的数量 int workingAxis = 0; + // 读取每个功能对应的轴索引 for (int function = 0; function < maxFunction; function++) { int functionAxis = settings.value(_rgFunctionSettingsKey[function], -1).toInt(&convertOk); badSettings |= (!convertOk || (functionAxis >= _axisCount)); @@ -261,10 +280,10 @@ void Joystick::_loadSettings() badSettings |= (workingAxis < 4); - // FunctionAxis mappings are always stored in TX mode 2 - // Remap to stored TX mode in settings + // 将轴映射从 TX 模式 2 重新映射到存储的 TX 模式 _remapAxes (2, _transmitterMode, _rgFunctionAxis); + // 读取每个按钮的动作设置 for (int button = 0; button < _totalButtonCount; button++) { const QString buttonAction = settings.value(QString(_buttonActionNameKey).arg(button), QString()).toString(); if (buttonAction.isEmpty() || (buttonAction == _buttonActionNone)) { @@ -284,6 +303,7 @@ void Joystick::_loadSettings() qCDebug(JoystickLog) << Q_FUNC_INFO << "button:action" << button << _buttonActionArray[button]->action << _buttonActionArray[button]->repeat; } + // 如果设置有误,标记为未校准 if (badSettings) { _calibrated = false; settings.setValue(_calibratedSettingsKey, false); @@ -296,6 +316,7 @@ void Joystick::_saveButtonSettings() settings.beginGroup(_settingsGroup); settings.beginGroup(_name); + // 保存每个按钮的动作和重复属性 for (int button = 0; button < _totalButtonCount; button++) { if (_buttonActionArray[button]) { settings.setValue(QString(_buttonActionNameKey).arg(button), _buttonActionArray[button]->action); @@ -311,10 +332,12 @@ void Joystick::_saveSettings() settings.beginGroup(_settingsGroup); if (_txModeSettingsKey) { + // 保存遥控器模式 settings.setValue(_txModeSettingsKey, _transmitterMode); } settings.beginGroup(_name); + // 保存各种参数 settings.setValue(_calibratedSettingsKey, _calibrated); settings.setValue(_exponentialSettingsKey, _exponential); settings.setValue(_accumulatorSettingsKey, _accumulator); @@ -327,12 +350,14 @@ void Joystick::_saveSettings() qCDebug(JoystickLog) << Q_FUNC_INFO << "calibrated:throttlemode:deadband:txmode" << _calibrated << _throttleMode << _deadband << _circleCorrection << _transmitterMode; + // 模板字符串,用于构建轴校准参数的键名 const QString minTpl("Axis%1Min"); const QString maxTpl("Axis%1Max"); const QString trimTpl("Axis%1Trim"); const QString revTpl("Axis%1Rev"); const QString deadbndTpl("Axis%1Deadbnd"); + // 保存每个轴的校准参数 for (int axis = 0; axis < _axisCount; axis++) { Calibration_t* calibration = &_rgCalibration[axis]; settings.setValue(trimTpl.arg(axis), calibration->center); @@ -355,16 +380,27 @@ void Joystick::_saveSettings() int temp[maxFunction]; _remapAxes(_transmitterMode, 2, temp); + // 保存每个功能对应的轴索引 for (int function = 0; function < maxFunction; function++) { settings.setValue(_rgFunctionSettingsKey[function], temp[function]); qCDebug(JoystickLog) << Q_FUNC_INFO << "name:function:axis" << _name << function << _rgFunctionSettingsKey[function]; } + // 保存按钮设置 _saveButtonSettings(); } +/** + * @brief 根据遥控器模式映射功能 + * @param mode 遥控器模式 + * @param function 功能类型 + * @return 映射后的功能类型,无效模式返回 -1 + * + * 根据给定的遥控器模式和功能类型,返回映射后的功能类型 + */ int Joystick::_mapFunctionMode(int mode, int function) { + // 遥控器模式到功能类型的映射表 static constexpr const int mapping[][6] = { { yawFunction, pitchFunction, rollFunction, throttleFunction, gimbalPitchFunction, gimbalYawFunction }, { yawFunction, throttleFunction, rollFunction, pitchFunction, gimbalPitchFunction, gimbalYawFunction }, @@ -379,36 +415,64 @@ int Joystick::_mapFunctionMode(int mode, int function) return -1; } +/** + * @brief 重新映射轴 + * @param currentMode 当前的遥控器模式 + * @param newMode 新的遥控器模式 + * @param newMapping 存储新轴映射的数组 + * + * 根据当前和新的遥控器模式,重新映射轴的分配 + */ void Joystick::_remapAxes(int currentMode, int newMode, int (&newMapping)[maxFunction]) { int temp[maxFunction]; + // 进行轴映射 for (int function = 0; function < maxFunction; function++) { temp[_mapFunctionMode(newMode, function)] = _rgFunctionAxis[_mapFunctionMode(currentMode, function)]; } + // 将临时映射复制到新映射数组 for (int function = 0; function < maxFunction; function++) { newMapping[function] = temp[function]; } } +/** + * @brief 设置遥控器模式 + * @param mode 新的遥控器模式 + * + * 根据新的遥控器模式重新映射轴,并保存设置 + */ void Joystick::setTXMode(int mode) { if ((mode > 0) && (mode <= 4)) { + // 重新映射轴 _remapAxes(_transmitterMode, mode, _rgFunctionAxis); _transmitterMode = mode; + // 保存设置 _saveSettings(); } else { qCWarning(JoystickLog) << "Invalid mode:" << mode; } } +/** + * @brief 调整轴值范围 + * @param value 原始轴值 + * @param calibration 轴的校准参数 + * @param withDeadbands 是否应用死区 + * @return 调整后的轴值,范围在 -1.0 到 1.0 之间 + * + * 根据校准参数和是否应用死区,将原始轴值调整到 -1.0 到 1.0 的范围 + */ float Joystick::_adjustRange(int value, const Calibration_t &calibration, bool withDeadbands) { float valueNormalized; float axisLength; float axisBasis; + // 根据轴值与中心点的关系计算基准值、归一化值和轴长度 if (value > calibration.center) { axisBasis = 1.0f; valueNormalized = value - calibration.center; @@ -421,48 +485,67 @@ float Joystick::_adjustRange(int value, const Calibration_t &calibration, bool w float axisPercent; if (withDeadbands) { + // 应用死区计算轴百分比 if (valueNormalized > calibration.deadband) { axisPercent = (valueNormalized - calibration.deadband) / (axisLength - calibration.deadband); - } else if (valueNormalized<-calibration.deadband) { + } else if (valueNormalized < -calibration.deadband) { axisPercent = (valueNormalized + calibration.deadband) / (axisLength - calibration.deadband); } else { axisPercent = 0.f; } } else { + // 不应用死区计算轴百分比 axisPercent = valueNormalized / axisLength; } + // 计算校正后的轴值 float correctedValue = axisBasis * axisPercent; if (calibration.reversed) { correctedValue *= -1.0f; } + // 将轴值限制在 -1.0 到 1.0 之间 return std::max(-1.0f, std::min(correctedValue, 1.0f)); } +/** + * @brief 线程运行函数 + * + * 打开游戏手柄,启动计时,循环更新按钮和轴状态,直到退出标志被设置 + */ void Joystick::run() { + // 打开游戏手柄 _open(); + // 启动轴计时 _axisTime.start(); + // 启动每个按钮动作的计时 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) { if (_buttonActionArray[buttonIndex]) { _buttonActionArray[buttonIndex]->buttonTime.start(); } } + // 循环更新按钮和轴状态,直到退出标志被设置 while (!_exitThread) { + // 更新游戏手柄状态 _update(); + // 处理按钮事件 _handleButtons(); if (axisCount() != 0) { + // 处理轴事件 _handleAxis(); } + // 计算线程休眠时间 const int sleep = qMin(static_cast(1000.0f / _maxAxisFrequencyHz), static_cast(1000.0f / _maxButtonFrequencyHz)) / 2; + // 线程休眠 QThread::msleep(sleep); } + // 关闭游戏手柄 _close(); } diff --git a/src/Joystick/Joystick.h b/src/Joystick/Joystick.h index e87168fa826906f211502994e277b0bd52ca044e..13f024f956e0d81f1c28aa5740b594e4e22c2be7 100644 --- a/src/Joystick/Joystick.h +++ b/src/Joystick/Joystick.h @@ -7,306 +7,717 @@ * ****************************************************************************/ +// 确保头文件只被编译一次,防止重复包含 #pragma once +// 包含 MAVLink 库头文件,用于与 MAVLink 协议进行交互 #include "MAVLinkLib.h" +// 包含 Qt 核心库和 QML 集成相关头文件 #include #include #include #include +// 声明日志记录类别,用于 Joystick 相关的日志输出 Q_DECLARE_LOGGING_CATEGORY(JoystickLog) +// 声明日志记录类别,用于 Joystick 值相关的日志输出 Q_DECLARE_LOGGING_CATEGORY(JoystickValuesLog) +// 前向声明类,避免头文件循环包含 class MavlinkActionManager; class QmlObjectListModel; class Vehicle; /*===========================================================================*/ +/** + * @brief 已分配的按钮动作类 + * + * 该类用于存储已分配给游戏手柄按钮的动作信息,包括动作名称、按钮按下时间和是否重复执行等。 + */ class AssignedButtonAction { public: + /** + * @brief 构造函数 + * @param name 动作名称 + */ AssignedButtonAction(const QString &name); + // 动作名称 QString action; + // 记录按钮按下的时间 QElapsedTimer buttonTime; + // 标记该动作是否重复执行 bool repeat = false; }; // TODO: Q_GADGET +/** + * @brief 可分配的按钮动作类 + * + * 该类继承自 QObject,用于表示可以分配给游戏手柄按钮的动作,包含动作名称和是否可重复执行的属性。 + */ class AssignableButtonAction : public QObject { Q_OBJECT + // 定义 QML 属性,用于获取动作名称,该属性为常量 Q_PROPERTY(QString action READ action CONSTANT) + // 定义 QML 属性,用于获取动作是否可重复执行,该属性为常量 Q_PROPERTY(bool canRepeat READ canRepeat CONSTANT) public: + /** + * @brief 构造函数 + * @param action_ 动作名称 + * @param canRepeat_ 动作是否可重复执行,默认为 false + * @param parent 父对象指针,默认为 nullptr + */ AssignableButtonAction(const QString &action_, bool canRepeat_ = false, QObject *parent = nullptr); + /** + * @brief 获取动作名称 + * @return 动作名称的常量引用 + */ const QString &action() const { return _action; } + /** + * @brief 判断动作是否可重复执行 + * @return 可重复执行返回 true,否则返回 false + */ bool canRepeat() const { return _repeat; } private: + // 动作名称 const QString _action; + // 标记动作是否可重复执行 const bool _repeat = false; }; /*===========================================================================*/ +/** + * @brief 游戏手柄类,继承自 QThread + * + * 该类用于管理游戏手柄的操作,包括游戏手柄的初始化、校准、按钮和轴值的读取, + * 以及将游戏手柄的操作转换为相应的动作信号等。 + */ class Joystick : public QThread { Q_OBJECT // QML_ELEMENT // QML_UNCREATABLE("") + // 告诉 MOC 在生成代码时包含相关头文件 Q_MOC_INCLUDE("QmlObjectListModel.h") Q_MOC_INCLUDE("Vehicle.h") + // 定义 QML 属性,用于获取和设置累加器状态,状态改变时发出信号 Q_PROPERTY(bool accumulator READ accumulator WRITE setAccumulator NOTIFY accumulatorChanged) + // 定义 QML 属性,用于获取游戏手柄是否已校准,校准状态改变时发出信号 Q_PROPERTY(bool calibrated MEMBER _calibrated NOTIFY calibratedChanged) + // 定义 QML 属性,用于获取和设置圆形校正状态,状态改变时发出信号 Q_PROPERTY(bool circleCorrection READ circleCorrection WRITE setCircleCorrection NOTIFY circleCorrectionChanged) + // 定义 QML 属性,用于获取和设置是否允许负推力,状态改变时发出信号 Q_PROPERTY(bool negativeThrust READ negativeThrust WRITE setNegativeThrust NOTIFY negativeThrustChanged) + // 定义 QML 属性,用于获取游戏手柄是否需要校准,该属性为常量 Q_PROPERTY(bool requiresCalibration READ requiresCalibration CONSTANT) + // 定义 QML 属性,用于获取和设置轴数据更新频率,频率改变时发出信号 Q_PROPERTY(float axisFrequencyHz READ axisFrequencyHz WRITE setAxisFrequency NOTIFY axisFrequencyHzChanged) + // 定义 QML 属性,用于获取和设置按钮数据更新频率,频率改变时发出信号 Q_PROPERTY(float buttonFrequencyHz READ buttonFrequencyHz WRITE setButtonFrequency NOTIFY buttonFrequencyHzChanged) + // 定义 QML 属性,用于获取和设置指数值,指数值改变时发出信号 Q_PROPERTY(float exponential READ exponential WRITE setExponential NOTIFY exponentialChanged) + // 定义 QML 属性,用于获取轴数据更新频率的最大值,该属性为常量 Q_PROPERTY(float maxAxisFrequencyHz MEMBER _maxAxisFrequencyHz CONSTANT) + // 定义 QML 属性,用于获取按钮数据更新频率的最大值,该属性为常量 Q_PROPERTY(float maxButtonFrequencyHz MEMBER _maxButtonFrequencyHz CONSTANT) + // 定义 QML 属性,用于获取轴数据更新频率的最小值,该属性为常量 Q_PROPERTY(float minAxisFrequencyHz MEMBER _minAxisFrequencyHz CONSTANT) + // 定义 QML 属性,用于获取按钮数据更新频率的最小值,该属性为常量 Q_PROPERTY(float minButtonFrequencyHz MEMBER _minButtonFrequencyHz CONSTANT) + // 定义 QML 属性,用于获取游戏手柄的轴数量,该属性为常量 Q_PROPERTY(int axisCount READ axisCount CONSTANT) + // 定义 QML 属性,用于获取和设置油门模式,模式改变时发出信号 Q_PROPERTY(int throttleMode READ throttleMode WRITE setThrottleMode NOTIFY throttleModeChanged) + // 定义 QML 属性,用于获取游戏手柄的总按钮数量,该属性为常量 Q_PROPERTY(int totalButtonCount READ totalButtonCount CONSTANT) + // 定义 QML 属性,用于获取可分配的动作列表,列表改变时发出信号 Q_PROPERTY(const QmlObjectListModel *assignableActions READ assignableActions NOTIFY assignableActionsChanged) + // 定义 QML 属性,用于获取禁用动作的名称,该属性为常量 Q_PROPERTY(QString disabledActionName READ disabledActionName CONSTANT) + // 定义 QML 属性,用于获取游戏手柄的名称,该属性为常量 Q_PROPERTY(QString name READ name CONSTANT) + // 定义 QML 属性,用于获取可分配动作的标题列表,列表改变时发出信号 Q_PROPERTY(QStringList assignableActionTitles READ assignableActionTitles NOTIFY assignableActionsChanged) + // 定义 QML 属性,用于获取按钮动作列表,列表改变时发出信号 Q_PROPERTY(QStringList buttonActions READ buttonActions NOTIFY buttonActionsChanged) + /** + * @brief 按钮事件类型枚举 + */ enum ButtonEvent_t { - BUTTON_UP, - BUTTON_DOWN, - BUTTON_REPEAT + BUTTON_UP, // 按钮抬起事件 + BUTTON_DOWN, // 按钮按下事件 + BUTTON_REPEAT // 按钮重复事件 }; public: + /** + * @brief 构造函数 + * @param name 游戏手柄名称 + * @param axisCount 游戏手柄的轴数量 + * @param buttonCount 游戏手柄的按钮数量 + * @param hatCount 游戏手柄的帽状开关数量 + * @param parent 父对象指针,默认为 nullptr + */ Joystick(const QString &name, int axisCount, int buttonCount, int hatCount, QObject *parent = nullptr); + /** + * @brief 析构函数 + */ virtual ~Joystick(); + /** + * @brief 校准参数结构体 + * + * 该结构体用于存储游戏手柄轴的校准参数,包括最小值、最大值、中心点、死区和是否反转等。 + */ struct Calibration_t { - int min = -32767; - int max = 32767; - int center = 0; - int deadband = 0; - bool reversed = false; + int min = -32767; // 轴的最小值 + int max = 32767; // 轴的最大值 + int center = 0; // 轴的中心点 + int deadband = 0; // 轴的死区范围 + bool reversed = false; // 轴是否反转 }; + /** + * @brief 轴功能类型枚举 + */ enum AxisFunction_t { - rollFunction, - pitchFunction, - yawFunction, - throttleFunction, - gimbalPitchFunction, - gimbalYawFunction, - maxFunction + rollFunction, // 横滚功能 + pitchFunction, // 俯仰功能 + yawFunction, // 偏航功能 + throttleFunction, // 油门功能 + gimbalPitchFunction, // 云台俯仰功能 + gimbalYawFunction, // 云台偏航功能 + maxFunction // 最大功能数 }; + /** + * @brief 油门模式枚举 + */ enum ThrottleMode_t { - ThrottleModeCenterZero, - ThrottleModeDownZero, - ThrottleModeMax + ThrottleModeCenterZero, // 油门中心为零模式 + ThrottleModeDownZero, // 油门底部为零模式 + ThrottleModeMax // 最大油门模式 }; + // QML 可调用函数,设置按钮的重复执行状态 Q_INVOKABLE void setButtonRepeat(int button, bool repeat); + // QML 可调用函数,获取按钮的重复执行状态 Q_INVOKABLE bool getButtonRepeat(int button); + // QML 可调用函数,设置按钮的动作 Q_INVOKABLE void setButtonAction(int button, const QString &action); + // QML 可调用函数,获取按钮的动作 Q_INVOKABLE QString getButtonAction(int button) const; + /** + * @brief 获取游戏手柄的名称 + * @return 游戏手柄的名称 + */ QString name() const { return _name; } + /** + * @brief 获取游戏手柄的总按钮数量 + * @return 游戏手柄的总按钮数量 + */ int totalButtonCount() const { return _totalButtonCount; } + /** + * @brief 获取游戏手柄的轴数量 + * @return 游戏手柄的轴数量 + */ int axisCount() const { return _axisCount; } + /** + * @brief 获取按钮动作列表 + * @return 按钮动作列表 + */ QStringList buttonActions() const; + /** + * @brief 获取可分配的动作列表 + * @return 可分配动作列表的指针 + */ const QmlObjectListModel *assignableActions() const { return _assignableButtonActions; } + /** + * @brief 获取可分配动作的标题列表 + * @return 可分配动作的标题列表 + */ QStringList assignableActionTitles() const { return _availableActionTitles; } + /** + * @brief 获取禁用动作的名称 + * @return 禁用动作的名称 + */ QString disabledActionName() const { return _buttonActionNone; } + /** + * @brief 停止游戏手柄的操作 + */ void stop(); - /// Start the polling thread which will in turn emit joystick signals + /** + * @brief 启动游戏手柄的轮询线程,该线程会发出游戏手柄相关信号 + * @param vehicle 关联的飞行器对象指针 + */ void startPolling(Vehicle *vehicle); + /** + * @brief 停止游戏手柄的轮询 + */ void stopPolling(); + /** + * @brief 设置指定轴的校准参数 + * @param axis 轴的索引 + * @param calibration 校准参数结构体 + */ void setCalibration(int axis, const Calibration_t &calibration); + /** + * @brief 获取指定轴的校准参数 + * @param axis 轴的索引 + * @return 校准参数结构体 + */ Calibration_t getCalibration(int axis) const; + /** + * @brief 设置轴的功能对应的轴索引 + * @param function 轴的功能类型 + * @param axis 轴的索引 + */ void setFunctionAxis(AxisFunction_t function, int axis); + /** + * @brief 获取轴的功能对应的轴索引 + * @param function 轴的功能类型 + * @return 轴的索引 + */ int getFunctionAxis(AxisFunction_t function) const; - // Joystick index used by sdl library - // Settable because sdl library remaps indices after certain events + // 游戏手柄索引,由 SDL 库使用 + // 可设置,因为 SDL 库在某些事件后会重新映射索引 // virtual int index(void) = 0; // virtual void setIndex(int index) = 0; - virtual bool requiresCalibration() const { return true; } + /** + * @brief 判断游戏手柄是否需要校准 + * @return 需要校准返回 true,否则返回 false + */ + virtual bool requiresCalibration() const { return true; } + /** + * @brief 获取油门模式 + * @return 油门模式的整数表示 + */ int throttleMode() const { return _throttleMode; } + /** + * @brief 设置油门模式 + * @param mode 油门模式的整数表示 + */ void setThrottleMode(int mode); + /** + * @brief 判断是否允许负推力 + * @return 允许返回 true,否则返回 false + */ bool negativeThrust() const { return _negativeThrust; } + /** + * @brief 设置是否允许负推力 + * @param allowNegative 允许负推力设置为 true,否则设置为 false + */ void setNegativeThrust(bool allowNegative); + /** + * @brief 获取指数值 + * @return 指数值 + */ float exponential() const { return _exponential; } + /** + * @brief 设置指数值 + * @param expo 指数值 + */ void setExponential(float expo); + /** + * @brief 判断累加器是否启用 + * @return 启用返回 true,否则返回 false + */ bool accumulator() const { return _accumulator; } + /** + * @brief 设置累加器的启用状态 + * @param accu 启用设置为 true,否则设置为 false + */ void setAccumulator(bool accu); + /** + * @brief 判断死区是否启用 + * @return 启用返回 true,否则返回 false + */ bool deadband() const { return _deadband; } + /** + * @brief 设置死区的启用状态 + * @param accu 启用设置为 true,否则设置为 false + */ void setDeadband(bool accu); + /** + * @brief 判断圆形校正是否启用 + * @return 启用返回 true,否则返回 false + */ bool circleCorrection() const { return _circleCorrection; } + /** + * @brief 设置圆形校正的启用状态 + * @param circleCorrection 启用设置为 true,否则设置为 false + */ void setCircleCorrection(bool circleCorrection); + /** + * @brief 获取遥控器模式 + * @return 遥控器模式的整数表示 + */ int getTXMode() const { return _transmitterMode; } + /** + * @brief 设置遥控器模式 + * @param mode 遥控器模式的整数表示 + */ void setTXMode(int mode); - /// Set the current calibration mode + /** + * @brief 设置当前的校准模式 + * @param calibrating 进入校准模式设置为 true,退出设置为 false + */ void setCalibrationMode(bool calibrating); - /// Get joystick message rate (in Hz) + /** + * @brief 获取游戏手柄轴数据的更新频率(Hz) + * @return 轴数据的更新频率 + */ float axisFrequencyHz() const { return _axisFrequencyHz; } - /// Set joystick message rate (in Hz) + /** + * @brief 设置游戏手柄轴数据的更新频率(Hz) + * @param val 轴数据的更新频率 + */ void setAxisFrequency(float val); - /// Get joystick button repeat rate (in Hz) + /** + * @brief 获取游戏手柄按钮的重复频率(Hz) + * @return 按钮的重复频率 + */ float buttonFrequencyHz() const { return _buttonFrequencyHz; } - /// Set joystick button repeat rate (in Hz) + /** + * @brief 设置游戏手柄按钮的重复频率(Hz) + * @param val 按钮的重复频率 + */ void setButtonFrequency(float val); signals: - // The raw signals are only meant for use by calibration + // 原始信号仅用于校准 + // 原始轴值改变时发出的信号 void rawAxisValueChanged(int index, int value); + // 原始按钮按下状态改变时发出的信号 void rawButtonPressedChanged(int index, int pressed); + // 校准状态改变时发出的信号 void calibratedChanged(bool calibrated); + // 按钮动作列表改变时发出的信号 void buttonActionsChanged(); + // 可分配动作列表改变时发出的信号 void assignableActionsChanged(); + // 油门模式改变时发出的信号 void throttleModeChanged(int mode); + // 是否允许负推力状态改变时发出的信号 void negativeThrustChanged(bool allowNegative); + // 指数值改变时发出的信号 void exponentialChanged(float exponential); + // 累加器状态改变时发出的信号 void accumulatorChanged(bool accumulator); + // 启用状态改变时发出的信号 void enabledChanged(bool enabled); + // 圆形校正状态改变时发出的信号 void circleCorrectionChanged(bool circleCorrection); + // 轴值改变时发出的信号 void axisValues(float roll, float pitch, float yaw, float throttle); + // 轴数据更新频率改变时发出的信号 void axisFrequencyHzChanged(); + // 按钮数据更新频率改变时发出的信号 void buttonFrequencyHzChanged(); + // 开始连续缩放时发出的信号 void startContinuousZoom(int direction); + // 停止连续缩放时发出的信号 void stopContinuousZoom(); + // 步进缩放时发出的信号 void stepZoom(int direction); + // 步进切换相机时发出的信号 void stepCamera(int direction); + // 步进切换视频流时发出的信号 void stepStream(int direction); + // 触发相机拍照时发出的信号 void triggerCamera(); + // 开始录制视频时发出的信号 void startVideoRecord(); + // 停止录制视频时发出的信号 void stopVideoRecord(); + // 切换视频录制状态时发出的信号 void toggleVideoRecord(); + // 云台俯仰开始移动时发出的信号 void gimbalPitchStart(int direction); + // 云台偏航开始移动时发出的信号 void gimbalYawStart(int direction); + // 云台俯仰停止移动时发出的信号 void gimbalPitchStop(); + // 云台偏航停止移动时发出的信号 void gimbalYawStop(); + // 云台居中时发出的信号 void centerGimbal(); + // 云台偏航锁定状态改变时发出的信号 void gimbalYawLock(bool lock); + // 设置飞行器武装状态时发出的信号 void setArmed(bool arm); + // 设置垂直起降飞行器进入前飞模式时发出的信号 void setVtolInFwdFlight(bool set); + // 设置飞行模式时发出的信号 void setFlightMode(const QString &flightMode); + // 紧急停止时发出的信号 void emergencyStop(); + // 执行夹爪动作时发出的信号 void gripperAction(GRIPPER_ACTIONS gripperAction); + // 起落架展开时发出的信号 void landingGearDeploy(); + // 起落架收回时发出的信号 void landingGearRetract(); + // 电机互锁启用时发出的信号 void motorInterlock(bool enable); + // 未知动作发生时发出的信号 void unknownAction(const QString &action); protected: + /** + * @brief 设置默认的校准参数 + */ void _setDefaultCalibration(); + // 游戏手柄名称 QString _name; + // 游戏手柄的轴数量 int _axisCount = 0; + // 游戏手柄的按钮数量 int _buttonCount = 0; + // 游戏手柄的帽状开关数量 int _hatCount = 0; private slots: + /** + * @brief 当激活的飞行器改变时调用的槽函数 + * @param activeVehicle 新的激活飞行器对象指针 + */ void _activeVehicleChanged(Vehicle *activeVehicle); + /** + * @brief 当飞行器数量改变时调用的槽函数 + * @param count 新的飞行器数量 + */ void _vehicleCountChanged(int count); + /** + * @brief 当飞行模式改变时调用的槽函数,用于重建动作列表 + */ void _flightModesChanged() { _buildActionList(_activeVehicle); } private: + /** + * @brief 打开游戏手柄设备,纯虚函数,由子类实现 + * @return 打开成功返回 true,否则返回 false + */ virtual bool _open() = 0; + /** + * @brief 关闭游戏手柄设备,纯虚函数,由子类实现 + */ virtual void _close() = 0; + /** + * @brief 更新游戏手柄的状态,纯虚函数,由子类实现 + * @return 更新成功返回 true,否则返回 false + */ virtual bool _update() = 0; + /** + * @brief 获取指定按钮的状态,纯虚函数,由子类实现 + * @param i 按钮的索引 + * @return 按钮按下返回 true,否则返回 false + */ virtual bool _getButton(int i) const = 0; + /** + * @brief 获取指定轴的值,纯虚函数,由子类实现 + * @param i 轴的索引 + * @return 轴的值 + */ virtual int _getAxis(int i) const = 0; + /** + * @brief 获取指定帽状开关的状态,纯虚函数,由子类实现 + * @param hat 帽状开关的索引 + * @param i 帽状开关的方向索引 + * @return 该方向按下返回 true,否则返回 false + */ virtual bool _getHat(int hat, int i) const = 0; + /** + * @brief 线程执行函数,重写 QThread 的 run 函数 + */ void run() override; + /** + * @brief 保存游戏手柄的设置 + */ void _saveSettings(); + /** + * @brief 保存游戏手柄按钮的设置 + */ void _saveButtonSettings(); + /** + * @brief 加载游戏手柄的设置 + */ void _loadSettings(); - /// Adjust the raw axis value to the -1:1 range given calibration information + + /** + * @brief 根据校准信息将原始轴值调整到 -1 到 1 的范围 + * @param value 原始轴值 + * @param calibration 校准参数结构体 + * @param withDeadbands 是否应用死区 + * @return 调整后的轴值 + */ float _adjustRange(int value, const Calibration_t &calibration, bool withDeadbands); + /** + * @brief 执行按钮动作 + * @param action 要执行的动作名称 + * @param buttonDown 按钮是否按下 + */ void _executeButtonAction(const QString &action, bool buttonDown); + /** + * @brief 查找可分配的按钮动作的索引 + * @param action 动作名称 + * @return 动作的索引,未找到返回 -1 + */ int _findAssignableButtonAction(const QString &action); + /** + * @brief 判断轴索引是否有效 + * @param axis 轴的索引 + * @return 有效返回 true,否则返回 false + */ bool _validAxis(int axis) const; + /** + * @brief 判断按钮索引是否有效 + * @param button 按钮的索引 + * @return 有效返回 true,否则返回 false + */ bool _validButton(int button) const; + /** + * @brief 处理游戏手柄轴的操作 + */ void _handleAxis(); + /** + * @brief 处理游戏手柄按钮的操作 + */ void _handleButtons(); + /** + * @brief 构建动作列表 + * @param activeVehicle 激活的飞行器对象指针 + */ void _buildActionList(Vehicle *activeVehicle); + /** + * @brief 更新遥控器模式的设置键 + * @param activeVehicle 激活的飞行器对象指针 + */ void _updateTXModeSettingsKey(Vehicle *activeVehicle); - /// Relative mappings of axis functions between different TX modes + + /** + * @brief 根据不同的遥控器模式映射轴功能 + * @param mode 遥控器模式 + * @param function 轴的功能类型 + * @return 映射后的轴索引 + */ int _mapFunctionMode(int mode, int function); - /// Remap current axis functions from current TX mode to new TX mode + + /** + * @brief 将当前轴功能从当前遥控器模式重新映射到新的遥控器模式 + * @param currentMode 当前的遥控器模式 + * @param newMode 新的遥控器模式 + * @param newMapping 存储新映射的数组 + */ void _remapAxes(int currentMode, int newMode, int (&newMapping)[maxFunction]); + // 帽状开关对应的按钮数量 int _hatButtonCount = 0; + // 游戏手柄的总按钮数量 int _totalButtonCount = 0; + // 存储轴值的数组指针 int *_rgAxisValues = nullptr; + // 存储校准参数的数组指针 Calibration_t *_rgCalibration = nullptr; + // 存储按钮状态的数组指针 uint8_t *_rgButtonValues = nullptr; + // MAVLink 动作管理器指针 MavlinkActionManager *_mavlinkActionManager = nullptr; + // 可分配按钮动作列表的指针 QmlObjectListModel *_assignableButtonActions = nullptr; + // 累加器是否启用 bool _accumulator = false; + // 游戏手柄是否已校准 bool _calibrated = false; + // 是否处于校准模式 bool _calibrationMode = false; + // 圆形校正是否启用 bool _circleCorrection = true; + // 死区是否启用 bool _deadband = false; + // 是否允许负推力 bool _negativeThrust = false; + // 是否为校准启动轮询 bool _pollingStartedForCalibration = false; + // 轴数据的更新频率 float _axisFrequencyHz = _defaultAxisFrequencyHz; + // 按钮数据的更新频率 float _buttonFrequencyHz = _defaultButtonFrequencyHz; + // 指数值 float _exponential = 0; + // 存储轴功能对应的轴索引的数组 int _rgFunctionAxis[maxFunction] = {}; + // 记录轴数据更新时间的定时器 QElapsedTimer _axisTime; + // 存储已分配按钮动作的列表 QList _buttonActionArray; + // 可分配动作的标题列表 QStringList _availableActionTitles; - std::atomic _exitThread = false; ///< true: signal thread to exit + + // 原子变量,用于信号线程退出 + std::atomic _exitThread = false; + // 油门模式 ThrottleMode_t _throttleMode = ThrottleModeDownZero; + // 激活的飞行器对象指针 Vehicle *_activeVehicle = nullptr; + // 遥控器模式的设置键 const char *_txModeSettingsKey = nullptr; + // 遥控器模式 static int _transmitterMode; + // 默认轴数据更新频率 static constexpr float _defaultAxisFrequencyHz = 25.0f; + // 默认按钮数据更新频率 static constexpr float _defaultButtonFrequencyHz = 5.0f; - // Arbitrary Limits + + // 轴数据更新频率的最小值 static constexpr float _minAxisFrequencyHz = 0.25f; + // 轴数据更新频率的最大值 static constexpr float _maxAxisFrequencyHz = 200.0f; + // 按钮数据更新频率的最小值 static constexpr float _minButtonFrequencyHz = 0.25f; + // 按钮数据更新频率的最大值 static constexpr float _maxButtonFrequencyHz = 50.0f; + // 轴功能对应的设置键数组 static constexpr const char *_rgFunctionSettingsKey[maxFunction] = { "RollAxis", "PitchAxis", @@ -316,54 +727,103 @@ private slots: "GimbalYawAxis" }; + // 设置组名称 static constexpr const char *_settingsGroup = "Joysticks"; + // 校准状态的设置键 static constexpr const char *_calibratedSettingsKey = "Calibrated4"; // Increment number to force recalibration + // 按钮动作名称的设置键 static constexpr const char *_buttonActionNameKey = "ButtonActionName%1"; + // 按钮动作重复状态的设置键 static constexpr const char *_buttonActionRepeatKey = "ButtonActionRepeat%1"; + // 油门模式的设置键 static constexpr const char *_throttleModeSettingsKey = "ThrottleMode"; + // 是否允许负推力的设置键 static constexpr const char *_negativeThrustSettingsKey = "NegativeThrust"; + // 指数值的设置键 static constexpr const char *_exponentialSettingsKey = "Exponential"; + // 累加器状态的设置键 static constexpr const char *_accumulatorSettingsKey = "Accumulator"; + // 死区状态的设置键 static constexpr const char *_deadbandSettingsKey = "Deadband"; + // 圆形校正状态的设置键 static constexpr const char *_circleCorrectionSettingsKey = "Circle_Correction"; + // 轴数据更新频率的设置键 static constexpr const char *_axisFrequencySettingsKey = "AxisFrequency"; + // 按钮数据更新频率的设置键 static constexpr const char *_buttonFrequencySettingsKey = "ButtonFrequency"; + // 固定翼飞行器遥控器模式的设置键 static constexpr const char *_fixedWingTXModeSettingsKey = "TXMode_FixedWing"; + // 多旋翼飞行器遥控器模式的设置键 static constexpr const char *_multiRotorTXModeSettingsKey = "TXMode_MultiRotor"; + // 地面车辆遥控器模式的设置键 static constexpr const char *_roverTXModeSettingsKey = "TXMode_Rover"; + // 垂直起降飞行器遥控器模式的设置键 static constexpr const char *_vtolTXModeSettingsKey = "TXMode_VTOL"; + // 水下航行器遥控器模式的设置键 static constexpr const char *_submarineTXModeSettingsKey = "TXMode_Submarine"; + // 无动作的名称 static constexpr const char *_buttonActionNone = QT_TR_NOOP("No Action"); + // 武装动作的名称 static constexpr const char *_buttonActionArm = QT_TR_NOOP("Arm"); + // 解除武装动作的名称 static constexpr const char *_buttonActionDisarm = QT_TR_NOOP("Disarm"); + // 切换武装状态动作的名称 static constexpr const char *_buttonActionToggleArm = QT_TR_NOOP("Toggle Arm"); + // 垂直起降飞行器切换到固定翼模式动作的名称 static constexpr const char *_buttonActionVTOLFixedWing = QT_TR_NOOP("VTOL: Fixed Wing"); + // 垂直起降飞行器切换到多旋翼模式动作的名称 static constexpr const char *_buttonActionVTOLMultiRotor = QT_TR_NOOP("VTOL: Multi-Rotor"); + // 连续放大动作的名称 static constexpr const char *_buttonActionContinuousZoomIn = QT_TR_NOOP("Continuous Zoom In"); + // 连续缩小动作的名称 static constexpr const char *_buttonActionContinuousZoomOut = QT_TR_NOOP("Continuous Zoom Out"); + // 步进放大动作的名称 static constexpr const char *_buttonActionStepZoomIn = QT_TR_NOOP("Step Zoom In"); + // 步进缩小动作的名称 static constexpr const char *_buttonActionStepZoomOut = QT_TR_NOOP("Step Zoom Out"); + // 切换到下一个视频流动作的名称 static constexpr const char *_buttonActionNextStream = QT_TR_NOOP("Next Video Stream"); + // 切换到上一个视频流动作的名称 static constexpr const char *_buttonActionPreviousStream = QT_TR_NOOP("Previous Video Stream"); + // 切换到下一个相机动作的名称 static constexpr const char *_buttonActionNextCamera = QT_TR_NOOP("Next Camera"); + // 切换到上一个相机动作的名称 static constexpr const char *_buttonActionPreviousCamera = QT_TR_NOOP("Previous Camera"); + // 触发相机拍照动作的名称 static constexpr const char *_buttonActionTriggerCamera = QT_TR_NOOP("Trigger Camera"); + // 开始录制视频动作的名称 static constexpr const char *_buttonActionStartVideoRecord = QT_TR_NOOP("Start Recording Video"); + // 停止录制视频动作的名称 static constexpr const char *_buttonActionStopVideoRecord = QT_TR_NOOP("Stop Recording Video"); + // 切换视频录制状态动作的名称 static constexpr const char *_buttonActionToggleVideoRecord = QT_TR_NOOP("Toggle Recording Video"); + // 云台向下移动动作的名称 static constexpr const char *_buttonActionGimbalDown = QT_TR_NOOP("Gimbal Down"); + // 云台向上移动动作的名称 static constexpr const char *_buttonActionGimbalUp = QT_TR_NOOP("Gimbal Up"); + // 云台向左移动动作的名称 static constexpr const char *_buttonActionGimbalLeft = QT_TR_NOOP("Gimbal Left"); + // 云台向右移动动作的名称 static constexpr const char *_buttonActionGimbalRight = QT_TR_NOOP("Gimbal Right"); + // 云台居中动作的名称 static constexpr const char *_buttonActionGimbalCenter = QT_TR_NOOP("Gimbal Center"); + // 云台偏航锁定动作的名称 static constexpr const char *_buttonActionGimbalYawLock = QT_TR_NOOP("Gimbal Yaw Lock"); + // 云台偏航跟随动作的名称 static constexpr const char *_buttonActionGimbalYawFollow = QT_TR_NOOP("Gimbal Yaw Follow"); + // 紧急停止动作的名称 static constexpr const char *_buttonActionEmergencyStop = QT_TR_NOOP("Emergency Stop"); + // 夹爪抓取动作的名称 static constexpr const char *_buttonActionGripperGrab = QT_TR_NOOP("Gripper Close"); + // 夹爪释放动作的名称 static constexpr const char *_buttonActionGripperRelease = QT_TR_NOOP("Gripper Open"); + // 起落架展开动作的名称 static constexpr const char *_buttonActionLandingGearDeploy= QT_TR_NOOP("Landing gear deploy"); + // 起落架收回动作的名称 static constexpr const char *_buttonActionLandingGearRetract= QT_TR_NOOP("Landing gear retract"); + // 电机互锁启用动作的名称 static constexpr const char *_buttonActionMotorInterlockEnable= QT_TR_NOOP("Motor Interlock enable"); + // 电机互锁禁用动作的名称 static constexpr const char *_buttonActionMotorInterlockDisable= QT_TR_NOOP("Motor Interlock disable"); }; diff --git a/src/Joystick/JoystickManager.h b/src/Joystick/JoystickManager.h index 82dc06c5d1001e9939eb776e37645f9e01748d6a..d1e1e1e34d170a075f38d5d36b50efce114b7528 100644 --- a/src/Joystick/JoystickManager.h +++ b/src/Joystick/JoystickManager.h @@ -7,69 +7,155 @@ * ****************************************************************************/ +// 确保头文件只被编译一次,防止重复包含 #pragma once +// 包含 Qt 核心库的头文件 #include #include #include #include +// 声明日志记录类别,用于 JoystickManager 相关的日志输出 Q_DECLARE_LOGGING_CATEGORY(JoystickManagerLog) +// 前向声明 Joystick 和 QTimer 类,避免头文件循环包含 class Joystick; class QTimer; +/** + * @brief 游戏手柄管理器类,负责管理游戏手柄的检测、激活等操作 + * + * 该类提供了游戏手柄的管理功能,包括检测可用的游戏手柄、设置激活的游戏手柄等, + * 并通过信号槽机制与其他模块进行交互。 + */ class JoystickManager : public QObject { Q_OBJECT // QML_ELEMENT // QML_UNCREATABLE("") + // 告诉 MOC 在生成代码时包含 Joystick.h 文件 Q_MOC_INCLUDE("Joystick.h") + // 定义 QML 属性,用于获取可用游戏手柄列表,当可用游戏手柄变化时发出信号 Q_PROPERTY(QVariantList joysticks READ joysticks NOTIFY availableJoysticksChanged) + // 定义 QML 属性,用于获取可用游戏手柄名称列表,当可用游戏手柄变化时发出信号 Q_PROPERTY(QStringList joystickNames READ joystickNames NOTIFY availableJoysticksChanged) + // 定义 QML 属性,用于获取和设置激活的游戏手柄,当激活游戏手柄变化时发出信号 Q_PROPERTY(Joystick *activeJoystick READ activeJoystick WRITE setActiveJoystick NOTIFY activeJoystickChanged) + // 定义 QML 属性,用于获取和设置激活的游戏手柄名称,当激活游戏手柄名称变化时发出信号 Q_PROPERTY(QString activeJoystickName READ activeJoystickName WRITE setActiveJoystickName NOTIFY activeJoystickNameChanged) public: + /** + * @brief 构造函数 + * @param parent 父对象指针,默认为 nullptr + */ explicit JoystickManager(QObject *parent = nullptr); + /** + * @brief 析构函数 + */ ~JoystickManager(); + /** + * @brief 获取 JoystickManager 的单例实例 + * @return JoystickManager 的单例指针 + */ static JoystickManager *instance(); + /** + * @brief 注册 QML 类型 + */ static void registerQmlTypes(); + /** + * @brief 获取可用游戏手柄列表 + * @return 可用游戏手柄列表,以 QVariantList 形式返回 + */ QVariantList joysticks(); + /** + * @brief 获取可用游戏手柄名称列表 + * @return 可用游戏手柄名称列表,以 QStringList 形式返回 + */ QStringList joystickNames() const { return _name2JoystickMap.keys(); } + /** + * @brief 获取当前激活的游戏手柄 + * @return 当前激活的游戏手柄指针 + */ Joystick *activeJoystick(); + /** + * @brief 设置当前激活的游戏手柄 + * @param joystick 要设置为激活的游戏手柄指针 + */ void setActiveJoystick(Joystick *joystick); + /** + * @brief 获取当前激活的游戏手柄名称 + * @return 当前激活的游戏手柄名称 + */ QString activeJoystickName() const; + /** + * @brief 根据游戏手柄名称设置激活的游戏手柄 + * @param name 要激活的游戏手柄名称 + * @return 设置成功返回 true,失败返回 false + */ bool setActiveJoystickName(const QString &name); signals: + /** + * @brief 当激活的游戏手柄发生变化时发出的信号 + * @param joystick 新的激活游戏手柄指针 + */ void activeJoystickChanged(Joystick *joystick); + /** + * @brief 当激活的游戏手柄名称发生变化时发出的信号 + * @param name 新的激活游戏手柄名称 + */ void activeJoystickNameChanged(const QString &name); + /** + * @brief 当可用的游戏手柄列表发生变化时发出的信号 + */ void availableJoysticksChanged(); + /** + * @brief 用于触发更新可用游戏手柄列表的信号 + */ void updateAvailableJoysticksSignal(); public slots: + /** + * @brief 初始化游戏手柄管理器 + */ void init(); private slots: - // TODO: move this to the right place: JoystickSDL.cc and JoystickAndroid.cc respectively and call through Joystick.cc + /** + * @brief 更新可用的游戏手柄列表 + * + * TODO: 将此方法分别移到 JoystickSDL.cc 和 JoystickAndroid.cc 中,并通过 Joystick.cc 调用 + */ void _updateAvailableJoysticks(); private: + /** + * @brief 从设置中读取并设置激活的游戏手柄 + */ void _setActiveJoystickFromSettings(); + // 当前激活的游戏手柄指针 Joystick *_activeJoystick = nullptr; + // 游戏手柄名称到游戏手柄对象的映射 QMap _name2JoystickMap; + // 游戏手柄检测定时器计数器 int _joystickCheckTimerCounter = 0;; + // 游戏手柄检测定时器指针 QTimer *_joystickCheckTimer = nullptr; + // 游戏手柄检测定时器的间隔时间,单位为毫秒 static constexpr int kTimerInterval = 1000; + // 超时时间,单位为毫秒 static constexpr int kTimeout = 1000; + // 设置组名称,用于存储游戏手柄管理器的设置 static constexpr const char *_settingsGroup = "JoystickManager"; + // 设置键名称,用于存储激活的游戏手柄信息 static constexpr const char *_settingsKeyActiveJoystick = "ActiveJoystick"; }; diff --git a/src/MAVLink/CMakeLists.txt b/src/MAVLink/CMakeLists.txt index 15e37b0411fc1eb70f70c888416c586487620d2c..70e557874b6c58d004fdd717a35b996bc1f52c64 100644 --- a/src/MAVLink/CMakeLists.txt +++ b/src/MAVLink/CMakeLists.txt @@ -35,7 +35,8 @@ message(STATUS "Building MAVLink") CPMAddPackage( NAME mavlink - GIT_REPOSITORY ${QGC_MAVLINK_GIT_REPO} + # GIT_REPOSITORY ${QGC_MAVLINK_GIT_REPO} + GIT_REPOSITORY "https://gitee.com/workstudy_1/mavlink.git" GIT_TAG ${QGC_MAVLINK_GIT_TAG} ) diff --git a/src/MAVLink/LibEvents/CMakeLists.txt b/src/MAVLink/LibEvents/CMakeLists.txt index 35554453bc85b689adb0eb34626ab1380ce1d930..f6207fded49674d12e30579ad1c92b88351e3cb9 100644 --- a/src/MAVLink/LibEvents/CMakeLists.txt +++ b/src/MAVLink/LibEvents/CMakeLists.txt @@ -13,7 +13,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_ CPMAddPackage( NAME libevents - GITHUB_REPOSITORY mavlink/libevents + GIT_REPOSITORY https://gitee.com/open-source-software_1/libevents.git GIT_TAG main SOURCE_SUBDIR libs/cpp ) diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index dec93db903cbac00ff6ba2a83568a23632f913a8..cb492d881925e90c513f8ea2239a7085054ab710 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -16,24 +16,33 @@ * */ +// 包含 QGCApplication 类的头文件 #include "QGCApplication.h" +// 包含 Qt 核心模块的头文件,用于事件处理、文件操作等功能 #include #include #include #include #include +// 包含 Qt GUI 模块的头文件,用于字体数据库和图标操作 #include #include +// 包含 Qt 网络模块的头文件,用于网络代理设置 #include +// 包含 Qt QML 模块的头文件,用于 QML 应用引擎和上下文操作 #include #include +// 包含 Qt Quick 模块的头文件,用于图像提供者和窗口操作 #include #include +// 包含 Qt Quick Controls 2 模块的头文件,用于设置控件样式 #include +// 包含 Qt 核心模块的私有头文件 #include +// 包含项目自定义的头文件,用于不同功能模块 #include "QGCLogging.h" #include "AudioOutput.h" #include "AutoPilotPlugin.h" @@ -70,17 +79,21 @@ #include "VehicleComponent.h" #include "VideoManager.h" +// 如果未禁用 MAVLink 检查器功能,包含相应头文件 #ifndef QGC_DISABLE_MAVLINK_INSPECTOR #include "MAVLinkInspectorController.h" #endif +// 如果启用 3D 视图功能,包含相应头文件 #ifdef QGC_VIEWER3D #include "Viewer3DManager.h" #endif +// 如果未禁用串口链接功能,包含相应头文件 #ifndef QGC_NO_SERIAL_LINK #include "FirmwareUpgradeController.h" #include "SerialLink.h" #endif +// 如果是 Linux 系统且不是 Android 系统,包含系统相关头文件 #ifdef Q_OS_LINUX #ifndef Q_OS_ANDROID #include @@ -88,185 +101,236 @@ #endif #endif +// 定义 QGCApplication 日志类别 QGC_LOGGING_CATEGORY(QGCApplicationLog, "qgc.qgcapplication") -// Qml Singleton factories +// Qml 单例工厂 +/** + * @brief MAVLink 单例工厂函数 + * @param engine QML 引擎指针 + * @param scriptEngine JavaScript 引擎指针 + * @return QObject* QGCMAVLink 对象指针 + */ static QObject *mavlinkSingletonFactory(QQmlEngine*, QJSEngine*) { return new QGCMAVLink(); } +/** + * @brief QGCApplication 构造函数 + * @param argc 命令行参数数量 + * @param argv 命令行参数数组 + * @param unitTesting 是否进行单元测试 + * @param simpleBootTest 是否进行简单启动测试 + */ QGCApplication::QGCApplication(int &argc, char *argv[], bool unitTesting, bool simpleBootTest) : QApplication(argc, argv) , _runningUnitTests(unitTesting) , _simpleBootTest(simpleBootTest) { + // 启动计时器,用于记录应用启动后的时间 _msecsElapsedTime.start(); - // Setup for network proxy support + // 设置网络代理使用系统配置 QNetworkProxyFactory::setUseSystemConfiguration(true); - // Parse command line options - bool fClearSettingsOptions = false; // Clear stored settings - bool fClearCache = false; // Clear parameter/airframe caches - bool logging = false; // Turn on logging + // 解析命令行选项 + bool fClearSettingsOptions = false; // 是否清除存储的设置 + bool fClearCache = false; // 是否清除参数/机体缓存 + bool logging = false; // 是否开启日志记录 QString loggingOptions; + // 定义命令行选项数组 CmdLineOpt_t rgCmdLineOptions[] = { { "--clear-settings", &fClearSettingsOptions, nullptr }, { "--clear-cache", &fClearCache, nullptr }, { "--logging", &logging, &loggingOptions }, { "--fake-mobile", &_fakeMobile, nullptr }, { "--log-output", &_logOutput, nullptr }, - // Add additional command line option flags here + // 可在此添加额外的命令行选项标志 }; + // 调用函数解析命令行选项 ParseCmdLineOptions(argc, argv, rgCmdLineOptions, std::size(rgCmdLineOptions), false); - // Set up timer for delayed missing fact display - _missingParamsDelayedDisplayTimer.setSingleShot(true); - _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout); + // 设置延迟显示缺失参数的定时器 + _missingParamsDelayedDisplayTimer.setSingleShot(true); // 定时器单次触发 + _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout); // 设置定时器间隔 + // 连接定时器超时信号到缺失参数显示槽函数 (void) connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay); - // Set application information + // 设置应用程序信息 QString applicationName; if (_runningUnitTests || simpleBootTest) { - // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app - // name. Also we want to run unit tests with clean settings every time. + // 单元测试或简单启动测试时,使用不同的应用名称,避免与正常应用共享设置 applicationName = QStringLiteral("%1_unittest").arg(QGC_APP_NAME); } else { #ifdef QGC_DAILY_BUILD - // This gives daily builds their own separate settings space. Allowing you to use daily and stable builds - // side by side without daily screwing up your stable settings. + // 每日构建版本使用不同的应用名称,以便与稳定版本共存 applicationName = QStringLiteral("%1 Daily").arg(QGC_APP_NAME); #else applicationName = QGC_APP_NAME; #endif } - setApplicationName(applicationName); - setOrganizationName(QGC_ORG_NAME); - setOrganizationDomain(QGC_ORG_DOMAIN); - setApplicationVersion(QString(QGC_APP_VERSION_STR)); + setApplicationName(applicationName); // 设置应用名称 + setOrganizationName(QGC_ORG_NAME); // 设置组织名称 + setOrganizationDomain(QGC_ORG_DOMAIN); // 设置组织域名 + setApplicationVersion(QString(QGC_APP_VERSION_STR)); // 设置应用版本 + #ifdef Q_OS_LINUX + // Linux 系统设置窗口图标 setWindowIcon(QIcon(":/res/qgroundcontrol.ico")); #endif - // Set settings format + // 设置设置文件格式为 INI 格式 QSettings::setDefaultFormat(QSettings::IniFormat); QSettings settings; + // 输出设置文件位置和是否可写信息 qCDebug(QGCApplicationLog) << "Settings location" << settings.fileName() << "Is writable?:" << settings.isWritable(); if (!settings.isWritable()) { + // 若设置文件不可写,输出警告信息 qCWarning(QGCApplicationLog) << "Setings location is not writable"; } - // The setting will delete all settings on this boot + // 如果设置中包含删除所有设置的标志,则需要清除设置 fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey); if (_runningUnitTests || simpleBootTest) { - // Unit tests run with clean settings + // 单元测试或简单启动测试时,强制清除设置 fClearSettingsOptions = true; } if (fClearSettingsOptions) { - // User requested settings to be cleared on command line - settings.clear(); + // 用户通过命令行请求清除设置 + settings.clear(); // 清除所有设置 - // Clear parameter cache + // 清除参数缓存 QDir paramDir(ParameterManager::parameterCacheDir()); - paramDir.removeRecursively(); - paramDir.mkpath(paramDir.absolutePath()); + paramDir.removeRecursively(); // 递归删除参数缓存目录 + paramDir.mkpath(paramDir.absolutePath()); // 重新创建参数缓存目录 } else { - // Determine if upgrade message for settings version bump is required. Check and clear must happen before toolbox is started since - // that will write some settings. + // 判断设置版本升级是否需要显示升级消息。检查和清除操作必须在工具盒启动之前进行,因为工具盒启动会写入一些设置 if (settings.contains(_settingsVersionKey)) { if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) { - settings.clear(); - _settingsUpgraded = true; + settings.clear(); // 若设置版本不一致,清除所有设置 + _settingsUpgraded = true; // 标记设置已升级 } } } + // 更新设置版本 settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION); if (fClearCache) { + // 清除参数缓存 QDir dir(ParameterManager::parameterCacheDir()); - dir.removeRecursively(); + dir.removeRecursively(); // 递归删除参数缓存目录 + // 删除缓存的机体元数据文件 QFile airframe(cachedAirframeMetaDataFile()); airframe.remove(); + // 删除缓存的参数元数据文件 QFile parameter(cachedParameterMetaDataFile()); parameter.remove(); } - // Set up our logging filters + // 根据日志选项设置日志过滤规则 QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(loggingOptions); - // We need to set language as early as possible prior to loading on JSON files. + // 在加载 JSON 文件之前尽早设置语言 setLanguage(); #ifndef QGC_DAILY_BUILD + // 非每日构建版本检查是否有新版本 _checkForNewVersion(); #endif } +/** + * @brief 设置应用程序语言 + */ void QGCApplication::setLanguage() { + // 获取系统默认区域设置 _locale = QLocale::system(); + // 输出系统报告的区域设置信息 qCDebug(QGCApplicationLog) << "System reported locale:" << _locale << "; Name" << _locale.name() << "; Preffered (used in maps): " << (QLocale::system().uiLanguages().length() > 0 ? QLocale::system().uiLanguages()[0] : "None"); + // 检查是否有早期访问的区域设置 QLocale::Language possibleLocale = AppSettings::_qLocaleLanguageEarlyAccess(); if (possibleLocale != QLocale::AnyLanguage) { _locale = QLocale(possibleLocale); } - //-- We have specific fonts for Korean + // 如果区域设置为韩语,加载韩语字体 if (_locale == QLocale::Korean) { qCDebug(QGCApplicationLog) << "Loading Korean fonts" << _locale.name(); if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Regular") < 0) { + // 若字体加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Regular font"; } if(QFontDatabase::addApplicationFont(":/fonts/NanumGothic-Bold") < 0) { + // 若字体加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Could not load /fonts/NanumGothic-Bold font"; } } + // 输出正在加载的本地化信息 qCDebug(QGCApplicationLog) << "Loading localizations for" << _locale.name(); + // 移除之前安装的翻译器 removeTranslator(JsonHelper::translator()); removeTranslator(&_qgcTranslatorSourceCode); removeTranslator(&_qgcTranslatorQtLibs); if (_locale.name() != "en_US") { + // 设置默认区域设置 QLocale::setDefault(_locale); if (_qgcTranslatorQtLibs.load("qt_" + _locale.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { + // 加载 Qt 库的翻译文件并安装翻译器 installTranslator(&_qgcTranslatorQtLibs); } else { + // 若翻译文件加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Qt lib localization for" << _locale.name() << "is not present"; } if (_qgcTranslatorSourceCode.load(_locale, QLatin1String("qgc_source_"), "", ":/i18n")) { + // 加载源代码的翻译文件并安装翻译器 installTranslator(&_qgcTranslatorSourceCode); } else { + // 若翻译文件加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Error loading source localization for" << _locale.name(); } if (JsonHelper::translator()->load(_locale, QLatin1String("qgc_json_"), "", ":/i18n")) { + // 加载 JSON 文件的翻译文件并安装翻译器 installTranslator(JsonHelper::translator()); } else { + // 若翻译文件加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Error loading json localization for" << _locale.name(); } } if (_qmlAppEngine) { + // 若 QML 引擎存在,重新翻译界面 _qmlAppEngine->retranslate(); } + // 发出语言改变信号 emit languageChanged(_locale); } +/** + * @brief QGCApplication 析构函数 + */ QGCApplication::~QGCApplication() { } +/** + * @brief 执行正常应用程序运行和单元测试通用的初始化操作 + */ void QGCApplication::init() { + // 初始化设置管理器 SettingsManager::instance()->init(); + // 注册各个模块的 QML 类型 LinkManager::registerQmlTypes(); ParameterManager::registerQmlTypes(); QGroundControlQmlGlobal::registerQmlTypes(); @@ -283,6 +347,7 @@ void QGCApplication::init() Viewer3DManager::registerQmlTypes(); #endif + // 注册不可创建的 QML 类型 qmlRegisterUncreatableType("QGroundControl.Vehicle", 1, 0, "GimbalController", "Reference only"); #ifndef QGC_DISABLE_MAVLINK_INSPECTOR @@ -293,150 +358,200 @@ void QGCApplication::init() qmlRegisterType("QGroundControl.Controllers", 1, 0, "LogDownloadController"); qmlRegisterType("QGroundControl.Controllers", 1, 0, "MAVLinkConsoleController"); - qmlRegisterUncreatableType("QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", "Reference only"); qmlRegisterType("QGroundControl.Controllers", 1, 0, "ESP8266ComponentController"); qmlRegisterType("QGroundControl.Controllers", 1, 0, "SyslinkComponentController"); - qmlRegisterUncreatableType("QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", "Reference only"); #ifndef QGC_NO_SERIAL_LINK qmlRegisterType("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController"); #endif qmlRegisterType("QGroundControl.Controllers", 1, 0, "JoystickConfigController"); + // 注册单例 QML 类型 (void) qmlRegisterSingletonType("QGroundControl.ShapeFileHelper", 1, 0, "ShapeFileHelper", [](QQmlEngine *, QJSEngine *) { return new ShapeFileHelper(); }); qmlRegisterSingletonType("MAVLink", 1, 0, "MAVLink", mavlinkSingletonFactory); - // Although this should really be in _initForNormalAppBoot putting it here allowws us to create unit tests which pop up more easily + // 加载 Open Sans 字体 if(QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) { + // 若字体加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans font"; } if(QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) { + // 若字体加载失败,输出警告信息 qCWarning(QGCApplicationLog) << "Could not load /fonts/opensans-demibold font"; } if (_simpleBootTest) { - // Since GStream builds are so problematic we initialize video during the simple boot test - // to make sure it works and verfies plugin availability. + // 简单启动测试时初始化视频功能 _initVideo(); } else if (!_runningUnitTests) { + // 非单元测试时进行正常应用启动初始化 _initForNormalAppBoot(); } } +/** + * @brief 初始化视频相关功能 + */ void QGCApplication::_initVideo() { #ifdef QGC_GST_STREAMING - // Gstreamer video playback requires OpenGL + // Gstreamer 视频播放需要 OpenGL QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); #endif - QGCCorePlugin::instance(); // CorePlugin must be initialized before VideoManager for Video Cleanup + // 初始化核心插件,视频管理器清理依赖于核心插件 + QGCCorePlugin::instance(); + // 初始化视频管理器 VideoManager::instance(); + // 标记视频管理器已初始化 _videoManagerInitialized = true; } +/** + * @brief 为正常应用程序启动进行初始化 + */ void QGCApplication::_initForNormalAppBoot() { - _initVideo(); // GStreamer must be initialized before QmlEngine + // 初始化视频功能,GStreamer 必须在 QmlEngine 之前初始化 + _initVideo(); + // 设置 QML 控件样式为 Basic QQuickStyle::setStyle("Basic"); + // 初始化核心插件 QGCCorePlugin::instance()->init(); + // 初始化 MAVLink 协议 MAVLinkProtocol::instance()->init(); + // 初始化多飞行器管理器 MultiVehicleManager::instance()->init(); + // 创建 QML 应用引擎 _qmlAppEngine = QGCCorePlugin::instance()->createQmlApplicationEngine(this); + // 连接 QML 引擎对象创建失败信号到退出应用程序槽函数 QObject::connect(_qmlAppEngine, &QQmlApplicationEngine::objectCreationFailed, this, QCoreApplication::quit, Qt::QueuedConnection); + // 创建根窗口 QGCCorePlugin::instance()->createRootWindow(_qmlAppEngine); + // 初始化音频输出 AudioOutput::instance()->init(SettingsManager::instance()->appSettings()->audioMuted()); + // 初始化跟随功能 FollowMe::instance()->init(); + // 初始化位置管理器 QGCPositionManager::instance()->init(); + // 初始化链接管理器 LinkManager::instance()->init(); + // 初始化视频管理器 VideoManager::instance()->init(mainRootWindow()); - // Image provider for Optical Flow + // 添加光学流图像提供者 _qmlAppEngine->addImageProvider(_qgcImageProviderId, new QGCImageProvider()); - // Safe to show popup error messages now that main window is created + // 主窗口创建完成后,可以安全地在工具栏显示弹出错误消息 _showErrorsInToolbar = true; - #ifdef Q_OS_LINUX - #ifndef Q_OS_ANDROID - #ifndef QGC_NO_SERIAL_LINK - if (!_runningUnitTests) { - // Determine if we have the correct permissions to access USB serial devices - QFile permFile("/etc/group"); - if(permFile.open(QIODevice::ReadOnly)) { - while(!permFile.atEnd()) { - const QString line = permFile.readLine(); - if (line.contains("dialout") && !line.contains(getenv("USER"))) { - permFile.close(); - showAppMessage(tr( - "The current user does not have the correct permissions to access serial devices. " - "You should also remove modemmanager since it also interferes.

" - "If you are using Ubuntu, execute the following commands to fix these issues:
" - "
sudo usermod -a -G dialout $USER
" - "sudo apt-get remove modemmanager
")); - break; - } + // 仅在 Linux 非 Android 系统,且未禁用串口链接功能,并且不是单元测试的情况下执行以下操作 +#ifdef Q_OS_LINUX +#ifndef Q_OS_ANDROID +#ifndef QGC_NO_SERIAL_LINK + if (!_runningUnitTests) { + // 检查当前用户是否有访问 USB 串口设备的权限 + QFile permFile("/etc/group"); + if(permFile.open(QIODevice::ReadOnly)) { + // 逐行读取文件内容 + while(!permFile.atEnd()) { + const QString line = permFile.readLine(); + // 检查当前行是否包含 "dialout" 且不包含当前用户 + if (line.contains("dialout") && !line.contains(getenv("USER"))) { + permFile.close(); + // 若权限不足,显示提示消息 + showAppMessage(tr( + "The current user does not have the correct permissions to access serial devices. " + "You should also remove modemmanager since it also interferes.

" + "If you are using Ubuntu, execute the following commands to fix these issues:
" + "
sudo usermod -a -G dialout $USER
" + "sudo apt-get remove modemmanager
")); + break; } - permFile.close(); } + // 关闭文件 + permFile.close(); } - #endif - #endif - #endif + } +#endif +#endif +#endif - // Now that main window is up check for lost log files + // 主窗口启动后,检查是否有丢失的日志文件 MAVLinkProtocol::instance()->checkForLostLogFiles(); - // Load known link configurations + // 加载已知的链接配置 LinkManager::instance()->loadLinkConfigurationList(); - // Probe for joysticks + // 初始化游戏手柄管理器,探测游戏手柄 JoystickManager::instance()->init(); + // 若设置已升级,显示设置重置消息 if (_settingsUpgraded) { showAppMessage(tr("The format for %1 saved settings has been modified. " "Your saved settings have been reset to defaults.").arg(applicationName())); } - // Connect links with flag AutoconnectLink + // 启动自动连接的链接 LinkManager::instance()->startAutoConnectedLinks(); } +/** + * @brief 设置下次启动时删除所有设置的标志 + */ void QGCApplication::deleteAllSettingsNextBoot() { QSettings settings; + // 设置删除所有设置的标志 settings.setValue(_deleteAllSettingsKey, true); } +/** + * @brief 清除下次启动时删除所有设置的标志 + */ void QGCApplication::clearDeleteAllSettingsNextBoot() { QSettings settings; + // 移除删除所有设置的标志 settings.remove(_deleteAllSettingsKey); } +/** + * @brief 报告缺失的参数 + * @param componentId 组件 ID + * @param name 参数名称 + */ void QGCApplication::reportMissingParameter(int componentId, const QString &name) { + // 创建包含组件 ID 和参数名称的键值对 const QPair missingParam(componentId, name); + // 若缺失参数列表中不包含该参数,则添加到列表中 if (!_missingParams.contains(missingParam)) { _missingParams.append(missingParam); } + // 启动延迟显示缺失参数的定时器 _missingParamsDelayedDisplayTimer.start(); } +/** + * @brief 显示缺失的参数信息 + */ void QGCApplication::_missingParamsDisplay() { + // 若缺失参数列表为空,则直接返回 if (_missingParams.isEmpty()) { return; } QString params; + // 遍历缺失参数列表,拼接参数信息 for (QPair& missingParam: _missingParams) { const QString param = QStringLiteral("%1:%2").arg(missingParam.first).arg(missingParam.second); if (params.isEmpty()) { @@ -444,15 +559,21 @@ void QGCApplication::_missingParamsDisplay() } else { params += QStringLiteral(", %1").arg(param); } - } + // 清空缺失参数列表 _missingParams.clear(); + // 显示缺失参数的提示消息 showAppMessage(tr("Parameters are missing from firmware. You may be running a version of firmware which is not fully supported or your firmware has a bug in it. Missing params: %1").arg(params)); } +/** + * @brief 获取 QML 应用引擎的根对象 + * @return QObject* QML 应用引擎的根对象指针,若不存在则返回 nullptr + */ QObject *QGCApplication::_rootQmlObject() { + // 若 QML 应用引擎存在且有根对象,则返回第一个根对象 if (_qmlAppEngine && _qmlAppEngine->rootObjects().size()) { return _qmlAppEngine->rootObjects()[0]; } @@ -460,45 +581,63 @@ QObject *QGCApplication::_rootQmlObject() return nullptr; } +/** + * @brief 显示关键的飞行器消息 + * @param message 要显示的消息 + */ void QGCApplication::showCriticalVehicleMessage(const QString &message) { - // PreArm messages are handled by Vehicle and shown in Map + // PreArm 相关消息由 Vehicle 处理并在地图中显示,这里不处理 if (message.startsWith(QStringLiteral("PreArm")) || message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) { return; } QObject *const rootQmlObject = _rootQmlObject(); + // 若根对象存在且允许在工具栏显示错误消息,则调用 QML 方法显示消息 if (rootQmlObject && _showErrorsInToolbar) { QVariant varReturn; QVariant varMessage = QVariant::fromValue(message); QMetaObject::invokeMethod(rootQmlObject, "showCriticalVehicleMessage", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, varMessage)); } else if (runningUnitTests() || !_showErrorsInToolbar) { - // Unit tests can run without UI + // 单元测试可以在无 UI 的情况下运行,输出调试信息 qCDebug(QGCApplicationLog) << "QGCApplication::showCriticalVehicleMessage unittest" << message; } else { + // 内部错误,输出警告信息 qCWarning(QGCApplicationLog) << "Internal error"; } } +/** + * @brief 显示应用程序消息对话框 + * @param message 要显示的消息内容 + * @param title 消息对话框的标题,若为空则使用应用程序名称 + */ void QGCApplication::showAppMessage(const QString &message, const QString &title) { + // 若标题为空,则使用应用程序名称作为标题 const QString dialogTitle = title.isEmpty() ? applicationName() : title; QObject *const rootQmlObject = _rootQmlObject(); + // 若根对象存在,则调用 QML 方法显示消息对话框 if (rootQmlObject) { QVariant varReturn; QVariant varMessage = QVariant::fromValue(message); QMetaObject::invokeMethod(rootQmlObject, "_showMessageDialog", Q_RETURN_ARG(QVariant, varReturn), Q_ARG(QVariant, dialogTitle), Q_ARG(QVariant, varMessage)); } else if (runningUnitTests()) { - // Unit tests can run without UI + // 单元测试可以在无 UI 的情况下运行,输出调试信息 qCDebug(QGCApplicationLog) << "QGCApplication::showAppMessage unittest title:message" << dialogTitle << message; } else { - // UI isn't ready yet + // UI 尚未准备好,将消息添加到延迟显示列表,并在 200 毫秒后尝试显示 _delayedAppMessages.append(QPair(dialogTitle, message)); QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } } +/** + * @brief 显示需要重启应用程序的消息,带有防抖功能 + * @param message 要显示的消息内容 + * @param title 消息对话框的标题 + */ void QGCApplication::showRebootAppMessage(const QString &message, const QString &title) { static QTime lastRebootMessage; @@ -507,28 +646,40 @@ void QGCApplication::showRebootAppMessage(const QString &message, const QString const QTime previousTime = lastRebootMessage; lastRebootMessage = currentTime; + // 若两次消息间隔小于 2 分钟,则不显示消息,实现防抖功能 if (previousTime.isValid() && (previousTime.msecsTo(currentTime) < (60 * 1000 * 2))) { // Debounce reboot messages return; } + // 显示应用程序消息 showAppMessage(message, title); } +/** + * @brief 显示延迟的应用程序消息 + */ void QGCApplication::_showDelayedAppMessages() { + // 若根对象存在,则依次显示延迟的应用程序消息,并清空延迟消息列表 if (_rootQmlObject()) { for (const QPair& appMsg: _delayedAppMessages) { showAppMessage(appMsg.second, appMsg.first); } _delayedAppMessages.clear(); } else { + // 若根对象不存在,200 毫秒后再次尝试显示 QTimer::singleShot(200, this, &QGCApplication::_showDelayedAppMessages); } } +/** + * @brief 获取主根窗口对象 + * @return QQuickWindow* 主根窗口对象指针,若不存在则尝试获取 + */ QQuickWindow *QGCApplication::mainRootWindow() { + // 若主根窗口对象不存在,则尝试从 QML 根对象转换获取 if (!_mainRootWindow) { _mainRootWindow = qobject_cast(_rootQmlObject()); } @@ -536,52 +687,80 @@ QQuickWindow *QGCApplication::mainRootWindow() return _mainRootWindow; } +/** + * @brief 显示飞行器配置界面 + */ void QGCApplication::showVehicleConfig() { + // 若根对象存在,则调用 QML 方法显示飞行器配置界面 if (_rootQmlObject()) { QMetaObject::invokeMethod(_rootQmlObject(), "showVehicleConfig"); } } +/** + * @brief 尝试通过 QML 关闭窗口 + */ void QGCApplication::qmlAttemptWindowClose() { + // 若根对象存在,则调用 QML 方法尝试关闭窗口 if (_rootQmlObject()) { QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose"); } } +/** + * @brief 检查是否有新版本的应用程序 + */ void QGCApplication::_checkForNewVersion() { + // 若正在进行单元测试,则不检查新版本 if (_runningUnitTests) { return; } + int _majorVersion, _minorVersion, _buildVersion; + // 解析当前应用程序版本号,若解析失败则不检查新版本 if (!_parseVersionText(applicationVersion(), _majorVersion, _minorVersion, _buildVersion)) { return; } + // 获取稳定版本检查文件的 URL const QString versionCheckFile = QGCCorePlugin::instance()->stableVersionCheckFileUrl(); if (!versionCheckFile.isEmpty()) { + // 创建文件下载对象 QGCFileDownload *const download = new QGCFileDownload(this); + // 连接下载完成信号到处理槽函数 (void) connect(download, &QGCFileDownload::downloadComplete, this, &QGCApplication::_qgcCurrentStableVersionDownloadComplete); + // 开始下载稳定版本检查文件 download->download(versionCheckFile); } } +/** + * @brief 处理 QGC 当前稳定版本文件下载完成的事件 + * @param remoteFile 远程文件的 URL + * @param localFile 本地下载文件的路径 + * @param errorMsg 下载错误消息,若为空则表示下载成功 + */ void QGCApplication::_qgcCurrentStableVersionDownloadComplete(const QString &remoteFile, const QString &localFile, const QString &errorMsg) { Q_UNUSED(remoteFile); + // 若下载成功 if (errorMsg.isEmpty()) { QFile versionFile(localFile); if (versionFile.open(QIODevice::ReadOnly)) { QTextStream textStream(&versionFile); + // 读取文件中的版本号 const QString version = textStream.readLine(); qCDebug(QGCApplicationLog) << version; int majorVersion, minorVersion, buildVersion; + // 解析下载的版本号 if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) { + // 比较当前版本和下载的版本号,若有新版本则显示提示消息 if (_majorVersion < majorVersion || ((_majorVersion == majorVersion) && (_minorVersion < minorVersion)) || ((_majorVersion == majorVersion) && (_minorVersion == minorVersion) && (_buildVersion < buildVersion))) { @@ -590,16 +769,28 @@ void QGCApplication::_qgcCurrentStableVersionDownloadComplete(const QString &rem } } } else { + // 下载失败,输出调试信息 qCDebug(QGCApplicationLog) << "Download QGC stable version failed" << errorMsg; } + // 释放发送者对象 sender()->deleteLater(); } +/** + * @brief 解析版本号文本 + * @param versionString 版本号字符串,格式应为 "vX.Y.Z" + * @param majorVersion 解析出的主版本号 + * @param minorVersion 解析出的次版本号 + * @param buildVersion 解析出的构建版本号 + * @return bool 解析成功返回 true,失败返回 false + */ bool QGCApplication::_parseVersionText(const QString &versionString, int &majorVersion, int &minorVersion, int &buildVersion) { + // 定义正则表达式,用于匹配版本号格式 static const QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)"); const QRegularExpressionMatch match = regExp.match(versionString); + // 若匹配成功且捕获到 3 个组,则解析版本号 if (match.hasMatch() && match.lastCapturedIndex() == 3) { majorVersion = match.captured(1).toInt(); minorVersion = match.captured(2).toInt(); @@ -610,22 +801,40 @@ bool QGCApplication::_parseVersionText(const QString &versionString, int &majorV return false; } +/** + * @brief 获取缓存的参数元数据文件路径 + * @return QString 缓存的参数元数据文件路径 + */ QString QGCApplication::cachedParameterMetaDataFile() { QSettings settings; + // 获取设置文件所在目录 const QDir parameterDir = QFileInfo(settings.fileName()).dir(); + // 返回缓存的参数元数据文件路径 return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.xml")); } +/** + * @brief 获取缓存的机体元数据文件路径 + * @return QString 缓存的机体元数据文件路径 + */ QString QGCApplication::cachedAirframeMetaDataFile() { QSettings settings; + // 获取设置文件所在目录 const QDir airframeDir = QFileInfo(settings.fileName()).dir(); + // 返回缓存的机体元数据文件路径 return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml")); } +/** + * @brief 获取信号在元对象中的索引 + * @param method 要检查的元方法 + * @return int 信号索引,若不是信号或出错则返回 -1 + */ int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method) { + // 若不是信号类型的元方法,输出警告信息并返回 -1 if (method.methodType() != QMetaMethod::Signal) { qCWarning(QGCApplicationLog) << "Internal error:" << Q_FUNC_INFO << "not a signal" << method.methodType(); return -1; @@ -633,6 +842,7 @@ int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method int index = -1; const QMetaObject *metaObject = method.enclosingMetaObject(); + // 遍历元对象中的方法,统计信号数量 for (int i=0; i<=method.methodIndex(); i++) { if (metaObject->method(i).methodType() != QMetaMethod::Signal) { continue; @@ -643,157 +853,102 @@ int QGCApplication::CompressedSignalList::_signalIndex(const QMetaMethod &method return index; } +/** + * @brief 向压缩信号列表中添加信号 + * @param method 要添加的信号对应的元方法 + */ void QGCApplication::CompressedSignalList::add(const QMetaMethod &method) { const QMetaObject *metaObject = method.enclosingMetaObject(); + // 获取信号索引 const int signalIndex = _signalIndex(method); + // 若信号索引有效且列表中不包含该信号,则添加到列表中 if (signalIndex != -1 && !contains(metaObject, signalIndex)) { _signalMap[method.enclosingMetaObject()].insert(signalIndex); } } +/** + * @brief 从压缩信号列表中移除信号 + * @param method 要移除的信号对应的元方法 + */ void QGCApplication::CompressedSignalList::remove(const QMetaMethod &method) { + // 获取信号索引 const int signalIndex = _signalIndex(method); const QMetaObject *const metaObject = method.enclosingMetaObject(); + // 若信号索引有效且列表中包含该信号,则移除该信号 if (signalIndex != -1 && _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex)) { _signalMap[metaObject].remove(signalIndex); + // 若该元对象下没有信号了,则从映射中移除该元对象 if (_signalMap[metaObject].count() == 0) { _signalMap.remove(metaObject); } } } +/** + * @brief 检查压缩信号列表中是否包含指定信号 + * @param metaObject 信号所在的元对象 + * @param signalIndex 信号索引 + * @return bool 包含返回 true,不包含返回 false + */ bool QGCApplication::CompressedSignalList::contains(const QMetaObject *metaObject, int signalIndex) { + // 检查映射中是否包含该元对象且该元对象下包含指定信号索引 return _signalMap.contains(metaObject) && _signalMap[metaObject].contains(signalIndex); } +/** + * @brief 向应用程序的压缩信号列表中添加信号 + * @param method 要添加的信号对应的元方法 + */ void QGCApplication::addCompressedSignal(const QMetaMethod &method) { + // 调用压缩信号列表的添加方法 _compressedSignals.add(method); } +/** + * @brief 从应用程序的压缩信号列表中移除信号 + * @param method 要移除的信号对应的元方法 + */ void QGCApplication::removeCompressedSignal(const QMetaMethod &method) { + // 调用压缩信号列表的移除方法 _compressedSignals.remove(method); } +/** + * @brief 压缩事件,避免重复处理相同的信号事件 + * @param event 要压缩的事件 + * @param receiver 事件接收者 + * @param postedEvents 已发布的事件列表 + * @return bool 若事件被压缩返回 true,否则返回基类的处理结果 + */ bool QGCApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) { + // 若事件类型不是 MetaCall 事件,则调用基类的压缩事件方法 if (event->type() != QEvent::MetaCall) { return QApplication::compressEvent(event, receiver, postedEvents); } const QMetaCallEvent *mce = static_cast(event); + // 若发送者为空或压缩信号列表中不包含该信号,则调用基类的压缩事件方法 if (!mce->sender() || !_compressedSignals.contains(mce->sender()->metaObject(), mce->signalId())) { return QApplication::compressEvent(event, receiver, postedEvents); } + // 遍历已发布的事件列表 for (QPostEventList::iterator it = postedEvents->begin(); it != postedEvents->end(); ++it) { QPostEvent &cur = *it; + // 若接收者不同、事件为空或事件类型不同,则跳过 if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type()) { continue; } const QMetaCallEvent *cur_mce = static_cast(cur.event); + // 若发送者、信号 ID 或事件 ID 不同,则跳过 if (cur_mce->sender() != mce->sender() || cur_mce->signalId() != mce->signalId() || cur_mce->id() != mce->id()) { - continue; - } - /* Keep The Newest Call */ - // We can't merely qSwap the existing posted event with the new one, since QEvent - // keeps track of whether it has been posted. Deletion of a formerly posted event - // takes the posted event list mutex and does a useless search of the posted event - // list upon deletion. We thus clear the QEvent::posted flag before deletion. - struct EventHelper : private QEvent { - static void clearPostedFlag(QEvent * ev) { - (&static_cast(ev)->t)[1] &= ~0x8001; // Hack to clear QEvent::posted - } - }; - EventHelper::clearPostedFlag(cur.event); - delete cur.event; - cur.event = event; - return true; - } - - return false; -} - -bool QGCApplication::event(QEvent *e) -{ - if (e->type() == QEvent::Quit) { - // On OSX if the user selects Quit from the menu (or Command-Q) the ApplicationWindow does not signal closing. Instead you get a Quit event here only. - // This in turn causes the standard QGC shutdown sequence to not run. So in this case we close the window ourselves such that the - // signal is sent and the normal shutdown sequence runs. - const bool forceClose = _mainRootWindow->property("_forceClose").toBool(); - qCDebug(QGCApplicationLog) << "Quit event" << forceClose; - // forceClose - // true: Standard QGC shutdown sequence is complete. Let the app quit normally by falling through to the base class processing. - // false: QGC shutdown sequence has not been run yet. Don't let this event close the app yet. Close the main window to kick off the normal shutdown. - if (!forceClose) { - // - _mainRootWindow->close(); - e->ignore(); - return true; - } - } - - return QApplication::event(e); -} - -QGCImageProvider *QGCApplication::qgcImageProvider() -{ - return dynamic_cast(_qmlAppEngine->imageProvider(_qgcImageProviderId)); -} - -void QGCApplication::shutdown() -{ - qCDebug(QGCApplicationLog) << "Exit"; - - if (_videoManagerInitialized) { - VideoManager::instance()->cleanup(); - } - - QGCCorePlugin::instance()->cleanup(); - - // This is bad, but currently qobject inheritances are incorrect and cause crashes on exit without - delete _qmlAppEngine; -} - -QString QGCApplication::numberToString(quint64 number) -{ - return getCurrentLanguage().toString(number); -} - -QString QGCApplication::bigSizeToString(quint64 size) -{ - QString result; - const QLocale kLocale = getCurrentLanguage(); - if (size < 1024) { - result = kLocale.toString(size) + "B"; - } else if (size < pow(1024, 2)) { - result = kLocale.toString(static_cast(size) / 1024.0, 'f', 1) + "KB"; - } else if (size < pow(1024, 3)) { - result = kLocale.toString(static_cast(size) / pow(1024, 2), 'f', 1) + "MB"; - } else if (size < pow(1024, 4)) { - result = kLocale.toString(static_cast(size) / pow(1024, 3), 'f', 1) + "GB"; - } else { - result = kLocale.toString(static_cast(size) / pow(1024, 4), 'f', 1) + "TB"; - } - return result; -} - -QString QGCApplication::bigSizeMBToString(quint64 size_MB) -{ - QString result; - const QLocale kLocale = getCurrentLanguage(); - if (size_MB < 1024) { - result = kLocale.toString(static_cast(size_MB) , 'f', 0) + " MB"; - } else if(size_MB < pow(1024, 2)) { - result = kLocale.toString(static_cast(size_MB) / 1024.0, 'f', 1) + " GB"; - } else { - result = kLocale.toString(static_cast(size_MB) / pow(1024, 2), 'f', 2) + " TB"; - } - return result; -} + continue; \ No newline at end of file diff --git a/src/QGCApplication.h b/src/QGCApplication.h index e4cd6ddf0b33d7564e90411b442a26f5f9f5b865..6ba8a1f826002c5b3dd856347b50e254b82b4e29 100644 --- a/src/QGCApplication.h +++ b/src/QGCApplication.h @@ -7,180 +7,406 @@ * ****************************************************************************/ +// 防止头文件被重复包含 #pragma once -#include -#include -#include -#include -#include +// 包含 Qt 核心模块的相关头文件 +#include // 用于测量时间间隔 +#include // 关联容器,存储键值对 +#include // 集合容器,存储唯一元素 +#include // 定时器类,用于定时触发事件 +#include // 翻译类,用于实现多语言支持 +// 包含 Qt 窗口部件模块的头文件 #include -class QQmlApplicationEngine; -class QQuickWindow; -class QGCImageProvider; -class QGCApplication; -class QEvent; -class QPostEventList; -class QMetaMethod; -class QMetaObject; - +// 前向声明类,告知编译器这些类的存在,减少头文件依赖 +class QQmlApplicationEngine; // QML 应用程序引擎类 +class QQuickWindow; // QML 快速窗口类 +class QGCImageProvider; // 自定义图像提供者类 +class QGCApplication; // 本类的前向声明 +class QEvent; // Qt 事件类 +class QPostEventList; // 待处理事件列表类 +class QMetaMethod; // Qt 元方法类 +class QMetaObject; // Qt 元对象类 + +// 如果已经定义了 qApp 宏,取消其定义 #if defined(qApp) #undef qApp #endif +// 重新定义 qApp 宏,将 QApplication 实例转换为 QGCApplication 指针 #define qApp (static_cast(QApplication::instance())) +// 如果已经定义了 qGuiApp 宏,取消其定义 #if defined(qGuiApp) #undef qGuiApp #endif +// 重新定义 qGuiApp 宏,将 QGuiApplication 实例转换为 QGCApplication 指针 #define qGuiApp (static_cast(QGuiApplication::instance())) +// 定义 qgcApp() 宏,等同于 qApp #define qgcApp() qApp /// The main application and management class. /// Needs QApplication base to support QtCharts module. /// TODO: Use QtGraphs to convert to QGuiApplication +// 主应用程序和管理类,继承自 QApplication 以支持 QtCharts 模块 class QGCApplication : public QApplication { - Q_OBJECT + Q_OBJECT // 启用 Qt 元对象系统,支持信号与槽、属性系统等功能 /// Unit Test have access to creating and destroying singletons + // 友元类,允许 UnitTest 类访问 QGCApplication 的私有成员 friend class UnitTest; public: + /** + * @brief 构造函数 + * + * @param argc 命令行参数数量 + * @param argv 命令行参数数组 + * @param unitTesting 是否进行单元测试 + * @param simpleBootTest 是否进行简单启动测试 + */ QGCApplication(int &argc, char *argv[], bool unitTesting, bool simpleBootTest); + /** + * @brief 析构函数 + */ ~QGCApplication(); - /// Sets the persistent flag to delete all settings the next time QGroundControl is started. + /** + * @brief 设置持久化标志,下次启动 QGroundControl 时删除所有设置 + */ static void deleteAllSettingsNextBoot(); - /// Clears the persistent flag to delete all settings the next time QGroundControl is started. + /** + * @brief 清除持久化标志,下次启动 QGroundControl 时不删除所有设置 + */ static void clearDeleteAllSettingsNextBoot(); + /** + * @brief 判断是否正在运行单元测试 + * + * @return true 正在运行单元测试 + * @return false 未运行单元测试 + */ bool runningUnitTests() const { return _runningUnitTests; } + /** + * @brief 判断是否正在进行简单启动测试 + * + * @return true 正在进行简单启动测试 + * @return false 未进行简单启动测试 + */ bool simpleBootTest() const { return _simpleBootTest; } - /// Returns true if Qt debug output should be logged to a file + /** + * @brief 判断是否应将 Qt 调试输出记录到文件 + * + * @return true 记录调试输出到文件 + * @return false 不记录调试输出到文件 + */ bool logOutput() const { return _logOutput; } - /// Used to report a missing Parameter. Warning will be displayed to user. Method may be called - /// multiple times. + /** + * @brief 报告缺失的参数,会向用户显示警告信息,该方法可多次调用 + * + * @param componentId 组件 ID + * @param name 参数名称 + */ void reportMissingParameter(int componentId, const QString &name); - /// @return true: Fake ui into showing mobile interface + /** + * @brief 判断是否模拟显示移动界面 + * + * @return true 模拟显示移动界面 + * @return false 不模拟显示移动界面 + */ bool fakeMobile() const { return _fakeMobile; } + /** + * @brief 设置应用程序语言 + */ void setLanguage(); + /** + * @brief 获取主根窗口指针 + * + * @return QQuickWindow* 主根窗口指针 + */ QQuickWindow *mainRootWindow(); + /** + * @brief 获取自启动以来经过的毫秒数 + * + * @return uint64_t 自启动以来经过的毫秒数 + */ uint64_t msecsSinceBoot() const { return _msecsElapsedTime.elapsed(); } + /** + * @brief 将无符号 64 位整数转换为字符串 + * + * @param number 待转换的无符号 64 位整数 + * @return QString 转换后的字符串 + */ QString numberToString(quint64 number); + /** + * @brief 将无符号 64 位整数表示的大文件大小转换为字符串 + * + * @param size 待转换的文件大小(字节) + * @return QString 转换后的字符串 + */ QString bigSizeToString(quint64 size); + /** + * @brief 将无符号 64 位整数表示的大文件大小(MB)转换为字符串 + * + * @param size_MB 待转换的文件大小(MB) + * @return QString 转换后的字符串 + */ QString bigSizeMBToString(quint64 size_MB); - /// Registers the signal such that only the last duplicate signal added is left in the queue. + /** + * @brief 注册信号,使得队列中只保留最后一个重复信号 + * + * @param method 要注册的元方法 + */ void addCompressedSignal(const QMetaMethod &method); + /** + * @brief 移除已注册的信号 + * + * @param method 要移除的元方法 + */ void removeCompressedSignal(const QMetaMethod &method); + /** + * @brief 事件处理函数 + * + * @param e 事件对象指针 + * @return bool 事件是否被处理 + */ bool event(QEvent *e) final; + /** + * @brief 获取缓存的参数元数据文件路径 + * + * @return QString 缓存的参数元数据文件路径 + */ static QString cachedParameterMetaDataFile(); + /** + * @brief 获取缓存的机体元数据文件路径 + * + * @return QString 缓存的机体元数据文件路径 + */ static QString cachedAirframeMetaDataFile(); public: - /// Perform initialize which is common to both normal application running and unit tests. + /** + * @brief 执行正常应用程序运行和单元测试通用的初始化操作 + */ void init(); + /** + * @brief 关闭应用程序,执行清理操作 + */ void shutdown(); - /// Although public, these methods are internal and should only be called by UnitTest code + /** + * @brief 获取 QML 应用程序引擎指针,该方法虽为 public,但仅供 UnitTest 代码调用 + * + * @return QQmlApplicationEngine* QML 应用程序引擎指针 + */ QQmlApplicationEngine *qmlAppEngine() const { return _qmlAppEngine; } signals: + /** + * @brief 语言发生变化时发出的信号 + * + * @param locale 新的区域设置 + */ void languageChanged(const QLocale locale); public slots: + /** + * @brief 显示飞行器配置界面 + */ void showVehicleConfig(); + /** + * @brief 尝试关闭 QML 窗口 + */ void qmlAttemptWindowClose(); - /// Get current language + /** + * @brief 获取当前语言的区域设置 + * + * @return QLocale 当前语言的区域设置 + */ QLocale getCurrentLanguage() const { return _locale; } - /// Show non-modal vehicle message to the user + /** + * @brief 向用户显示非模态的飞行器关键信息 + * + * @param message 要显示的信息 + */ void showCriticalVehicleMessage(const QString &message); - /// Show modal application message to the user + /** + * @brief 向用户显示模态的应用程序信息 + * + * @param message 要显示的信息 + * @param title 信息标题,默认为空字符串 + */ void showAppMessage(const QString &message, const QString &title = QString()); - /// Show modal application message to the user about the need for a reboot. Multiple messages will be supressed if they occur - /// one after the other. + /** + * @brief 向用户显示模态的应用程序重启信息,若连续出现多条信息,会进行抑制 + * + * @param message 要显示的信息 + * @param title 信息标题,默认为空字符串 + */ void showRebootAppMessage(const QString &message, const QString &title = QString()); + /** + * @brief 获取 QGC 图像提供者指针 + * + * @return QGCImageProvider* QGC 图像提供者指针 + */ QGCImageProvider *qgcImageProvider(); private slots: - /// Called when the delay timer fires to show the missing parameters warning + /** + * @brief 延迟定时器触发时调用,用于显示缺失参数警告信息 + */ void _missingParamsDisplay(); + /** + * @brief 当 QGC 当前稳定版本下载完成时调用 + * + * @param remoteFile 远程文件路径 + * @param localFile 本地文件路径 + * @param errorMsg 错误信息 + */ void _qgcCurrentStableVersionDownloadComplete(const QString &remoteFile, const QString &localFile, const QString &errorMsg); + /** + * @brief 解析版本字符串,提取主版本号、次版本号和构建版本号 + * + * @param versionString 版本字符串 + * @param majorVersion 输出参数,主版本号 + * @param minorVersion 输出参数,次版本号 + * @param buildVersion 输出参数,构建版本号 + * @return bool 解析成功返回 true,失败返回 false + */ static bool _parseVersionText(const QString &versionString, int &majorVersion, int &minorVersion, int &buildVersion); + /** + * @brief 显示延迟的应用程序信息 + */ void _showDelayedAppMessages(); private: + /** + * @brief 压缩事件,确保队列中只保留最后一个重复信号 + * + * @param event 事件对象指针 + * @param receiver 事件接收者指针 + * @param postedEvents 待处理事件列表指针 + * @return bool 事件是否被压缩 + */ bool compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) final; + /** + * @brief 初始化视频相关功能 + */ void _initVideo(); - /// Initialize the application for normal application boot. Or in other words we are not going to run unit tests. + /** + * @brief 为正常应用程序启动进行初始化,即不运行单元测试时的初始化操作 + */ void _initForNormalAppBoot(); + /** + * @brief 获取 QML 根对象指针 + * + * @return QObject* QML 根对象指针 + */ QObject *_rootQmlObject(); + /** + * @brief 检查是否有新版本 + */ void _checkForNewVersion(); - bool _runningUnitTests = false; - bool _simpleBootTest = false; - static constexpr int _missingParamsDelayedDisplayTimerTimeout = 1000; ///< Timeout to wait for next missing fact to come in before display - QTimer _missingParamsDelayedDisplayTimer; ///< Timer use to delay missing fact display - QList> _missingParams; ///< List of missing parameter component id:name - - QQmlApplicationEngine *_qmlAppEngine = nullptr; - bool _logOutput = false; ///< true: Log Qt debug output to file - bool _fakeMobile = false; ///< true: Fake ui into displaying mobile interface - bool _settingsUpgraded = false; ///< true: Settings format has been upgrade to new version - int _majorVersion = 0; - int _minorVersion = 0; - int _buildVersion = 0; - QQuickWindow *_mainRootWindow = nullptr; - QTranslator _qgcTranslatorSourceCode; ///< translations for source code C++/Qml - QTranslator _qgcTranslatorQtLibs; ///< tranlsations for Qt libraries - QLocale _locale; - bool _error = false; - bool _showErrorsInToolbar = false; - QElapsedTimer _msecsElapsedTime; - bool _videoManagerInitialized = false; - + bool _runningUnitTests = false; ///< 是否正在运行单元测试 + bool _simpleBootTest = false; ///< 是否正在进行简单启动测试 + // 等待下一个缺失参数到来的超时时间(毫秒),超时后显示警告信息 + static constexpr int _missingParamsDelayedDisplayTimerTimeout = 1000; + QTimer _missingParamsDelayedDisplayTimer; ///< 用于延迟显示缺失参数警告信息的定时器 + // 缺失参数列表,存储组件 ID 和参数名称的键值对 + QList> _missingParams; + + QQmlApplicationEngine *_qmlAppEngine = nullptr; ///< QML 应用程序引擎指针 + bool _logOutput = false; ///< 是否将 Qt 调试输出记录到文件 + bool _fakeMobile = false; ///< 是否模拟显示移动界面 + bool _settingsUpgraded = false; ///< 设置格式是否已升级到新版本 + int _majorVersion = 0; ///< 主版本号 + int _minorVersion = 0; ///< 次版本号 + int _buildVersion = 0; ///< 构建版本号 + QQuickWindow *_mainRootWindow = nullptr; ///< 主根窗口指针 + QTranslator _qgcTranslatorSourceCode; ///< 用于源代码(C++/QML)的翻译器 + QTranslator _qgcTranslatorQtLibs; ///< 用于 Qt 库的翻译器 + QLocale _locale; ///< 当前语言的区域设置 + bool _error = false; ///< 是否发生错误 + bool _showErrorsInToolbar = false; ///< 是否在工具栏显示错误信息 + QElapsedTimer _msecsElapsedTime; ///< 用于测量自启动以来经过的时间 + bool _videoManagerInitialized = false; ///< 视频管理器是否已初始化 + + // 延迟显示的应用程序信息列表,存储标题和信息的键值对 QList> _delayedAppMessages; + // 压缩信号列表类,用于管理压缩信号 class CompressedSignalList { public: + /** + * @brief 构造函数 + */ CompressedSignalList() {} + /** + * @brief 添加信号到压缩信号列表 + * + * @param method 要添加的元方法 + */ void add(const QMetaMethod &method); + /** + * @brief 从压缩信号列表中移除信号 + * + * @param method 要移除的元方法 + */ void remove(const QMetaMethod &method); + /** + * @brief 判断压缩信号列表中是否包含指定的信号 + * + * @param metaObject 元对象指针 + * @param signalIndex 信号索引 + * @return bool 包含返回 true,不包含返回 false + */ bool contains(const QMetaObject *metaObject, int signalIndex); private: - /// Returns a signal index that is can be compared to QMetaCallEvent.signalId + /** + * @brief 返回一个可与 QMetaCallEvent.signalId 比较的信号索引 + * + * @param method 元方法 + * @return int 信号索引 + */ static int _signalIndex(const QMetaMethod &method); + // 信号映射表,存储元对象指针和信号索引集合的键值对 QMap> _signalMap; + // 禁用拷贝构造函数和赋值运算符 Q_DISABLE_COPY(CompressedSignalList) }; - CompressedSignalList _compressedSignals; + CompressedSignalList _compressedSignals; ///< 压缩信号列表对象 - const QString _settingsVersionKey = QStringLiteral("SettingsVersion"); ///< Settings key which hold settings version - static constexpr const char *_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; ///< If this settings key is set on boot, all settings will be deleted + // 存储设置版本的设置键 + const QString _settingsVersionKey = QStringLiteral("SettingsVersion"); + // 如果此设置键在启动时存在,将删除所有设置 + static constexpr const char *_deleteAllSettingsKey = "DeleteAllSettingsNextBoot"; + // QGC 图像提供者的 ID const QString _qgcImageProviderId = QStringLiteral("QGCImages"); }; diff --git a/src/QmlControls/AppSettings.qml b/src/QmlControls/AppSettings.qml index 2ce1b26d559d3992f46f95d62e4d6dedc2a667da..59c820ae92638e6b6e8c38731d742d39bfd323af 100644 --- a/src/QmlControls/AppSettings.qml +++ b/src/QmlControls/AppSettings.qml @@ -7,32 +7,53 @@ * ****************************************************************************/ - +// 导入 Qt Quick 相关模块 import QtQuick import QtQuick.Controls import QtQuick.Layouts +// 导入 QGroundControl 自定义模块 import QGroundControl import QGroundControl.Palette import QGroundControl.Controls import QGroundControl.ScreenTools import QGroundControl.AppSettings +/** + * @brief 应用设置界面组件 + * + * 该组件用于展示应用的设置页面,包含左侧的设置按钮列表和右侧的设置内容面板。 + */ Rectangle { id: settingsView + // 设置矩形背景颜色为调色板中定义的窗口颜色 color: qgcPal.window + // 设置组件的 z 轴顺序,确保其显示在最上层 z: QGroundControl.zOrderTopMost + // 定义一个只读属性,用于存储默认文本的高度,该高度取自 ScreenTools 组件的 defaultFontPixelHeight 属性 readonly property real _defaultTextHeight: ScreenTools.defaultFontPixelHeight + // 定义一个只读属性,用于存储默认文本的宽度,该宽度取自 ScreenTools 组件的 defaultFontPixelWidth 属性 readonly property real _defaultTextWidth: ScreenTools.defaultFontPixelWidth + // 定义一个只读属性,用于存储水平边距,其值为默认文本宽度的一半 readonly property real _horizontalMargin: _defaultTextWidth / 2 + // 定义一个只读属性,用于存储垂直边距,其值为默认文本高度的一半 readonly property real _verticalMargin: _defaultTextHeight / 2 + // 定义一个只读属性,用于存储按钮的高度。如果当前屏幕是小屏幕,则按钮高度为默认文本高度的 3 倍;否则为 2 倍 readonly property real _buttonHeight: ScreenTools.isTinyScreen ? ScreenTools.defaultFontPixelHeight * 3 : ScreenTools.defaultFontPixelHeight * 2 + // 标识是否为首次加载 property bool _first: true + // 标识是否从远程 ID 设置页面跳转过来 property bool _commingFromRIDSettings: false + /** + * @brief 显示指定的设置页面 + * + * 遍历按钮列表,找到对应文本的按钮并触发点击事件。 + * @param settingsPage 要显示的设置页面名称 + */ function showSettingsPage(settingsPage) { for (var i=0; i +// 包含 Qt 标准路径头文件 #include +// 包含 Qt 目录操作头文件 #include +// 包含 Qt 设置头文件 #include -// Release languages are 90%+ complete +// 已发布的语言列表,翻译完成度在 90% 以上 QList AppSettings::_rgReleaseLanguages = { - QLocale::English, - QLocale::Azerbaijani, - QLocale::Chinese, - QLocale::Japanese, - QLocale::Korean, - QLocale::Portuguese, - QLocale::Russian, + QLocale::English, // 英语 + QLocale::Azerbaijani, // 阿塞拜疆语 + QLocale::Chinese, // 中文 + QLocale::Japanese, // 日语 + QLocale::Korean, // 韩语 + QLocale::Portuguese, // 葡萄牙语 + QLocale::Russian, // 俄语 }; -// Partial languages are 40%+ complete +// 部分完成的语言列表,翻译完成度在 40% 以上 QList AppSettings::_rgPartialLanguages = { - QLocale::Ukrainian, + QLocale::Ukrainian, // 乌克兰语 }; +// 语言信息数组,包含语言 ID 和对应的显示名称 AppSettings::LanguageInfo_t AppSettings::_rgLanguageInfo[] = { - { QLocale::AnyLanguage, "System" }, // Must be first - { QLocale::Azerbaijani, "Azerbaijani (Azerbaijani)" }, - { QLocale::Bulgarian, "български (Bulgarian)" }, - { QLocale::Chinese, "中文 (Chinese)" }, - { QLocale::Dutch, "Nederlands (Dutch)" }, - { QLocale::English, "English" }, - { QLocale::Finnish, "Suomi (Finnish)" }, - { QLocale::French, "Français (French)" }, - { QLocale::German, "Deutsche (German)" }, - { QLocale::Greek, "Ελληνικά (Greek)" }, - { QLocale::Hebrew, "עברית (Hebrew)" }, - { QLocale::Italian, "Italiano (Italian)" }, - { QLocale::Japanese, "日本語 (Japanese)" }, - { QLocale::Korean, "한국어 (Korean)" }, - { QLocale::NorwegianBokmal, "Norsk (Norwegian)" }, - { QLocale::Polish, "Polskie (Polish)" }, - { QLocale::Portuguese, "Português (Portuguese)" }, - { QLocale::Russian, "Pусский (Russian)" }, - { QLocale::Spanish, "Español (Spanish)" }, - { QLocale::Swedish, "Svenska (Swedish)" }, - { QLocale::Turkish, "Türk (Turkish)" } + { QLocale::AnyLanguage, "System" }, // 系统默认语言,必须放在第一个位置 + { QLocale::Azerbaijani, "Azerbaijani (Azerbaijani)" }, // 阿塞拜疆语 + { QLocale::Bulgarian, "български (Bulgarian)" }, // 保加利亚语 + { QLocale::Chinese, "中文 (Chinese)" }, // 中文 + { QLocale::Dutch, "Nederlands (Dutch)" }, // 荷兰语 + { QLocale::English, "English" }, // 英语 + { QLocale::Finnish, "Suomi (Finnish)" }, // 芬兰语 + { QLocale::French, "Français (French)" }, // 法语 + { QLocale::German, "Deutsche (German)" }, // 德语 + { QLocale::Greek, "Ελληνικά (Greek)" }, // 希腊语 + { QLocale::Hebrew, "עברית (Hebrew)" }, // 希伯来语 + { QLocale::Italian, "Italiano (Italian)" }, // 意大利语 + { QLocale::Japanese, "日本語 (Japanese)" }, // 日语 + { QLocale::Korean, "한국어 (Korean)" }, // 韩语 + { QLocale::NorwegianBokmal, "Norsk (Norwegian)" }, // 挪威语 + { QLocale::Polish, "Polskie (Polish)" }, // 波兰语 + { QLocale::Portuguese, "Português (Portuguese)" }, // 葡萄牙语 + { QLocale::Russian, "Pусский (Russian)" }, // 俄语 + { QLocale::Spanish, "Español (Spanish)" }, // 西班牙语 + { QLocale::Swedish, "Svenska (Swedish)" }, // 瑞典语 + { QLocale::Turkish, "Türk (Turkish)" } // 土耳其语 }; +/** + * @brief 声明应用设置组 + * + * 注册 QML 类型,设置全局主题,处理旧版设置键的迁移, + * 根据平台和用户设置初始化保存路径,并连接相关信号和槽。 + */ DECLARE_SETTINGGROUP(App, "") { + // 向 QML 注册 AppSettings 类型,仅作为引用使用 qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "AppSettings", "Reference only"); + // 根据室内调色板设置,设置全局主题为暗色或亮色 QGCPalette::setGlobalTheme(indoorPalette()->rawValue().toBool() ? QGCPalette::Dark : QGCPalette::Light); + // 创建 QSettings 对象,用于读写应用程序设置 QSettings settings; - // These two "type" keys were changed to "class" values + // 旧版固件类型和载具类型的键名 static const char* deprecatedFirmwareTypeKey = "offlineEditingFirmwareType"; static const char* deprecatedVehicleTypeKey = "offlineEditingVehicleType"; + // 如果存在旧版固件类型键,将其值转换为固件类并更新 if (settings.contains(deprecatedFirmwareTypeKey)) { settings.setValue(deprecatedFirmwareTypeKey, QGCMAVLink::firmwareClass(static_cast(settings.value(deprecatedFirmwareTypeKey).toInt()))); } + // 如果存在旧版载具类型键,将其值转换为载具类并更新 if (settings.contains(deprecatedVehicleTypeKey)) { settings.setValue(deprecatedVehicleTypeKey, QGCMAVLink::vehicleClass(static_cast(settings.value(deprecatedVehicleTypeKey).toInt()))); } + // 旧版键名列表 QStringList deprecatedKeyNames = { "virtualJoystickCentralized", "offlineEditingFirmwareType", "offlineEditingVehicleType" }; + // 新版键名列表 QStringList newKeyNames = { "virtualJoystickAutoCenterThrottle", "offlineEditingFirmwareClass", "offlineEditingVehicleClass" }; + // 进入应用设置组 settings.beginGroup(_settingsGroup); + // 遍历旧版键名列表,将旧键值迁移到新键,并移除旧键 for (int i=0; i(savePath()); + // 获取应用程序名称 QString appName = QCoreApplication::applicationName(); #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - // Mobile builds always use the runtime generated location for savePath. + // 移动平台(Android 或 iOS)总是使用运行时生成的保存路径,用户未修改过保存路径 bool userHasModifiedSavePath = false; #else + // 非移动平台,检查用户是否修改过保存路径 bool userHasModifiedSavePath = !savePathFact->rawValue().toString().isEmpty() || !_nameToMetaDataMap[savePathName]->rawDefaultValue().toString().isEmpty(); #endif + // 如果用户未修改过保存路径 if (!userHasModifiedSavePath) { #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #ifdef Q_OS_IOS - // This will expose the directories directly to the File iOs app + // iOS 平台,获取文档目录并设置为保存路径 QDir rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); savePathFact->setRawValue(rootDir.absolutePath()); #else + // Android 平台,获取保存路径 QString rootDirPath; #ifdef Q_OS_ANDROID + // 如果设置了保存到 SD 卡 if (androidSaveToSDCard()->rawValue().toBool()) { - rootDirPath = AndroidInterface::getSDCardPath(); + // 获取 SD 卡路径 + rootDirPath = AndroidInterface::getSDCardPath(); qDebug() << "AndroidInterface::getSDCardPath();" << rootDirPath; - if (rootDirPath.isEmpty() || !QDir(rootDirPath).exists()) { - rootDirPath.clear(); - qgcApp()->showAppMessage(AppSettings::tr("Save to SD card specified for application data. But no SD card present. Using internal storage.")); - } else if (!QFileInfo(rootDirPath).isWritable()) { - rootDirPath.clear(); - qgcApp()->showAppMessage(AppSettings::tr("Save to SD card specified for application data. But SD card is write protected. Using internal storage.")); - } + // 如果 SD 卡路径不存在或不可写,使用内部存储 + if (rootDirPath.isEmpty() || !QDir(rootDirPath).exists()) { + rootDirPath.clear(); + qgcApp()->showAppMessage(AppSettings::tr("Save to SD card specified for application data. But no SD card present. Using internal storage.")); + } else if (!QFileInfo(rootDirPath).isWritable()) { + rootDirPath.clear(); + qgcApp()->showAppMessage(AppSettings::tr("Save to SD card specified for application data. But SD card is write protected. Using internal storage.")); } + } #endif + // 如果 SD 卡路径无效,使用通用数据目录 if (rootDirPath.isEmpty()) { rootDirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); } + // 设置保存路径为应用程序在通用数据目录下的子目录 savePathFact->setRawValue(QDir(rootDirPath).filePath(appName)); #endif + // 隐藏保存路径设置项 savePathFact->setVisible(false); #else - QDir rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); - savePathFact->setRawValue(rootDir.filePath(appName)); + // 非移动平台,获取文档目录并设置为应用程序的保存路径 + QDir rootDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); + savePathFact->setRawValue(rootDir.filePath(appName)); #endif } + // 连接保存路径改变信号到保存路径改变信号和检查保存路径目录槽函数 connect(savePathFact, &Fact::rawValueChanged, this, &AppSettings::savePathsChanged); connect(savePathFact, &Fact::rawValueChanged, this, &AppSettings::_checkSavePathDirectories); + // 检查保存路径目录是否存在,不存在则创建 _checkSavePathDirectories(); } +// 声明离线编辑固件类设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingFirmwareClass) +// 声明离线编辑载具类设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingVehicleClass) +// 声明离线编辑巡航速度设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingCruiseSpeed) +// 声明离线编辑悬停速度设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingHoverSpeed) +// 声明离线编辑上升速度设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingAscentSpeed) +// 声明离线编辑下降速度设置项 DECLARE_SETTINGSFACT(AppSettings, offlineEditingDescentSpeed) +// 声明电池剩余百分比通知设置项 DECLARE_SETTINGSFACT(AppSettings, batteryPercentRemainingAnnounce) +// 声明默认任务项高度设置项 DECLARE_SETTINGSFACT(AppSettings, defaultMissionItemAltitude) +// 声明音频静音设置项 DECLARE_SETTINGSFACT(AppSettings, audioMuted) +// 声明虚拟摇杆启用设置项 DECLARE_SETTINGSFACT(AppSettings, virtualJoystick) +// 声明虚拟摇杆油门自动回中设置项 DECLARE_SETTINGSFACT(AppSettings, virtualJoystickAutoCenterThrottle) +// 声明虚拟摇杆左手模式设置项 DECLARE_SETTINGSFACT(AppSettings, virtualJoystickLeftHandedMode) +// 声明应用程序字体大小设置项 DECLARE_SETTINGSFACT(AppSettings, appFontPointSize) +// 声明保存路径设置项 DECLARE_SETTINGSFACT(AppSettings, savePath) +// 声明 Android 保存到 SD 卡设置项 DECLARE_SETTINGSFACT(AppSettings, androidSaveToSDCard) +// 声明使用检查清单设置项 DECLARE_SETTINGSFACT(AppSettings, useChecklist) +// 声明强制使用检查清单设置项 DECLARE_SETTINGSFACT(AppSettings, enforceChecklist) +// 声明启用多载具面板设置项 DECLARE_SETTINGSFACT(AppSettings, enableMultiVehiclePanel) +// 声明 Mapbox 令牌设置项 DECLARE_SETTINGSFACT(AppSettings, mapboxToken) +// 声明 Mapbox 账户设置项 DECLARE_SETTINGSFACT(AppSettings, mapboxAccount) +// 声明 Mapbox 样式设置项 DECLARE_SETTINGSFACT(AppSettings, mapboxStyle) +// 声明 Esri 令牌设置项 DECLARE_SETTINGSFACT(AppSettings, esriToken) +// 声明自定义 URL 设置项 DECLARE_SETTINGSFACT(AppSettings, customURL) +// 声明 VWorld 令牌设置项 DECLARE_SETTINGSFACT(AppSettings, vworldToken) +// 声明 GStreamer 调试级别设置项 DECLARE_SETTINGSFACT(AppSettings, gstDebugLevel) +// 声明跟随目标设置项 DECLARE_SETTINGSFACT(AppSettings, followTarget) +// 声明禁用所有持久化设置项 DECLARE_SETTINGSFACT(AppSettings, disableAllPersistence) +// 声明首次运行提示 ID 显示设置项 DECLARE_SETTINGSFACT(AppSettings, firstRunPromptIdsShown) +// 声明 AirLink 登录名设置项 DECLARE_SETTINGSFACT(AppSettings, loginAirLink) +// 声明 AirLink 密码设置项 DECLARE_SETTINGSFACT(AppSettings, passAirLink) +/** + * @brief 声明室内调色板设置项 + * + * 如果室内调色板设置项未创建,则创建该设置项,并连接其值改变信号到室内调色板改变槽函数。 + */ DECLARE_SETTINGSFACT_NO_FUNC(AppSettings, indoorPalette) { if (!_indoorPaletteFact) { _indoorPaletteFact = _createSettingsFact(indoorPaletteName); + // 连接室内调色板值改变信号到室内调色板改变槽函数 connect(_indoorPaletteFact, &Fact::rawValueChanged, this, &AppSettings::_indoorPaletteChanged); } return _indoorPaletteFact; } +/** + * @brief 声明应用程序语言设置项 + * + * 如果应用程序语言设置项未创建,则创建该设置项,连接其值改变信号到应用程序语言改变槽函数, + * 并根据已发布语言、部分完成语言和测试语言列表设置枚举信息。 + */ DECLARE_SETTINGSFACT_NO_FUNC(AppSettings, qLocaleLanguage) { if (!_qLocaleLanguageFact) { _qLocaleLanguageFact = _createSettingsFact(qLocaleLanguageName); + // 连接应用程序语言值改变信号到应用程序语言改变槽函数 connect(_qLocaleLanguageFact, &Fact::rawValueChanged, this, &AppSettings::_qLocaleLanguageChanged); + // 获取应用程序语言设置项的元数据 FactMetaData* metaData = _qLocaleLanguageFact->metaData(); + // 枚举字符串列表 QStringList rgEnumStrings; + // 枚举值列表 QVariantList rgEnumValues; - // System is always an available selection + // 系统默认语言总是可用选项 rgEnumStrings.append(_rgLanguageInfo[0].languageName); rgEnumValues.append(_rgLanguageInfo[0].languageId); + // 添加已发布语言到枚举列表 for (const auto& languageInfo: _rgLanguageInfo) { if (_rgReleaseLanguages.contains(languageInfo.languageId)) { rgEnumStrings.append(languageInfo.languageName); rgEnumValues.append(languageInfo.languageId); } } + // 添加部分完成语言到枚举列表,并标记为 (Partial) for (const auto& languageInfo: _rgLanguageInfo) { if (_rgPartialLanguages.contains(languageInfo.languageId)) { rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Partial)")); @@ -206,7 +292,7 @@ DECLARE_SETTINGSFACT_NO_FUNC(AppSettings, qLocaleLanguage) } } #ifdef QGC_DAILY_BUILD - // Only daily builds include full set of languages for testing purposes + // 仅每日构建版本包含所有语言用于测试,标记为 (Test Only) for (const auto& languageInfo: _rgLanguageInfo) { if (!_rgReleaseLanguages.contains(languageInfo.languageId) && !_rgPartialLanguages.contains(languageInfo.languageId)) { rgEnumStrings.append(QString(languageInfo.languageName) + AppSettings::tr(" (Test Only)")); @@ -214,8 +300,10 @@ DECLARE_SETTINGSFACT_NO_FUNC(AppSettings, qLocaleLanguage) } } #endif + // 设置元数据的枚举信息 metaData->setEnumInfo(rgEnumStrings, rgEnumValues); + // 如果枚举索引无效,设置为系统默认语言 if (_qLocaleLanguageFact->enumIndex() == -1) { _qLocaleLanguageFact->setRawValue(QLocale::AnyLanguage); } @@ -223,37 +311,64 @@ DECLARE_SETTINGSFACT_NO_FUNC(AppSettings, qLocaleLanguage) return _qLocaleLanguageFact; } +/** + * @brief 应用程序语言改变槽函数 + * + * 当应用程序语言设置改变时,调用 QGC 应用程序的设置语言函数。 + */ void AppSettings::_qLocaleLanguageChanged() { qgcApp()->setLanguage(); } +/** + * @brief 检查保存路径目录 + * + * 检查保存路径目录是否存在,不存在则创建。并在保存路径目录存在的情况下, + * 创建各个子目录用于存储不同类型的文件。 + */ void AppSettings::_checkSavePathDirectories(void) { + // 获取保存路径目录 QDir savePathDir(savePath()->rawValue().toString()); + // 如果保存路径目录不存在,创建该目录 if (!savePathDir.exists()) { QDir().mkpath(savePathDir.absolutePath()); } + // 如果保存路径目录存在,创建各个子目录 if (savePathDir.exists()) { - savePathDir.mkdir(parameterDirectory); - savePathDir.mkdir(telemetryDirectory); - savePathDir.mkdir(missionDirectory); - savePathDir.mkdir(logDirectory); - savePathDir.mkdir(videoDirectory); - savePathDir.mkdir(photoDirectory); - savePathDir.mkdir(crashDirectory); - savePathDir.mkdir(mavlinkActionsDirectory); + savePathDir.mkdir(parameterDirectory); // 参数目录 + savePathDir.mkdir(telemetryDirectory); // 遥测数据目录 + savePathDir.mkdir(missionDirectory); // 任务目录 + savePathDir.mkdir(logDirectory); // 日志目录 + savePathDir.mkdir(videoDirectory); // 视频目录 + savePathDir.mkdir(photoDirectory); // 照片目录 + savePathDir.mkdir(crashDirectory); // 崩溃日志目录 + savePathDir.mkdir(mavlinkActionsDirectory); // Mavlink 动作目录 } } +/** + * @brief 室内调色板改变槽函数 + * + * 当室内调色板设置改变时,根据设置值设置全局主题为暗色或亮色。 + */ void AppSettings::_indoorPaletteChanged(void) { QGCPalette::setGlobalTheme(indoorPalette()->rawValue().toBool() ? QGCPalette::Dark : QGCPalette::Light); } +/** + * @brief 获取任务保存路径 + * + * 如果保存路径存在且有效,返回任务目录的路径;否则返回空字符串。 + * @return 任务保存路径 + */ QString AppSettings::missionSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回任务目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(missionDirectory); @@ -261,9 +376,17 @@ QString AppSettings::missionSavePath(void) return QString(); } +/** + * @brief 获取参数保存路径 + * + * 如果保存路径存在且有效,返回参数目录的路径;否则返回空字符串。 + * @return 参数保存路径 + */ QString AppSettings::parameterSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回参数目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(parameterDirectory); @@ -271,9 +394,17 @@ QString AppSettings::parameterSavePath(void) return QString(); } +/** + * @brief 获取遥测数据保存路径 + * + * 如果保存路径存在且有效,返回遥测数据目录的路径;否则返回空字符串。 + * @return 遥测数据保存路径 + */ QString AppSettings::telemetrySavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回遥测数据目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(telemetryDirectory); @@ -281,9 +412,17 @@ QString AppSettings::telemetrySavePath(void) return QString(); } +/** + * @brief 获取日志保存路径 + * + * 如果保存路径存在且有效,返回日志目录的路径;否则返回空字符串。 + * @return 日志保存路径 + */ QString AppSettings::logSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回日志目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(logDirectory); @@ -291,9 +430,17 @@ QString AppSettings::logSavePath(void) return QString(); } +/** + * @brief 获取视频保存路径 + * + * 如果保存路径存在且有效,返回视频目录的路径;否则返回空字符串。 + * @return 视频保存路径 + */ QString AppSettings::videoSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回视频目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(videoDirectory); @@ -301,9 +448,17 @@ QString AppSettings::videoSavePath(void) return QString(); } +/** + * @brief 获取照片保存路径 + * + * 如果保存路径存在且有效,返回照片目录的路径;否则返回空字符串。 + * @return 照片保存路径 + */ QString AppSettings::photoSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回照片目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(photoDirectory); @@ -311,9 +466,17 @@ QString AppSettings::photoSavePath(void) return QString(); } +/** + * @brief 获取崩溃日志保存路径 + * + * 如果保存路径存在且有效,返回崩溃日志目录的路径;否则返回空字符串。 + * @return 崩溃日志保存路径 + */ QString AppSettings::crashSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回崩溃日志目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(crashDirectory); @@ -321,9 +484,17 @@ QString AppSettings::crashSavePath(void) return QString(); } +/** + * @brief 获取 Mavlink 动作保存路径 + * + * 如果保存路径存在且有效,返回 Mavlink 动作目录的路径;否则返回空字符串。 + * @return Mavlink 动作保存路径 + */ QString AppSettings::mavlinkActionsSavePath(void) { + // 获取保存路径 QString path = savePath()->rawValue().toString(); + // 如果保存路径不为空且目录存在,返回 Mavlink 动作目录路径 if (!path.isEmpty() && QDir(path).exists()) { QDir dir(path); return dir.filePath(mavlinkActionsDirectory); @@ -331,53 +502,81 @@ QString AppSettings::mavlinkActionsSavePath(void) return QString(); } +/** + * @brief 将首次运行提示 ID 的 QVariant 字符串列表转换为整数列表 + * + * @param firstRunPromptIds 首次运行提示 ID 的 QVariant 字符串列表 + * @return 转换后的整数列表 + */ QList AppSettings::firstRunPromptsIdsVariantToList(const QVariant& firstRunPromptIds) { QList rgIds; + // 将 QVariant 转换为字符串列表,按逗号分隔 QStringList strIdList = firstRunPromptIds.toString().split(",", Qt::SkipEmptyParts); + // 遍历字符串列表,将每个字符串转换为整数并添加到整数列表 for (const QString& strId: strIdList) { rgIds.append(strId.toInt()); } return rgIds; } +/** + * @brief 将首次运行提示 ID 的整数列表转换为 QVariant 字符串列表 + * + * @param rgIds 首次运行提示 ID 的整数列表 + * @return 转换后的 QVariant 字符串列表 + */ QVariant AppSettings::firstRunPromptsIdsListToVariant(const QList& rgIds) { QStringList strList; + // 遍历整数列表,将每个整数转换为字符串并添加到字符串列表 for (int id: rgIds) { strList.append(QString::number(id)); } + // 将字符串列表用逗号连接并转换为 QVariant 返回 return QVariant(strList.join(",")); } +/** + * @brief 标记指定的首次运行提示 ID 为已显示 + * + * @param id 要标记的首次运行提示 ID + */ void AppSettings::firstRunPromptIdsMarkIdAsShown(int id) { + // 获取当前已显示的首次运行提示 ID 列表 QList rgIds = firstRunPromptsIdsVariantToList(firstRunPromptIdsShown()->rawValue()); + // 如果列表中不包含该 ID,将其添加到列表并更新设置项的值 if (!rgIds.contains(id)) { rgIds.append(id); firstRunPromptIdsShown()->setRawValue(firstRunPromptsIdsListToVariant(rgIds)); } } -/// Returns the current qLocaleLanguage setting bypassing the standard SettingsGroup path. It also validates -/// that the value is a supported language. This should only be used by QGCApplication::setLanguage to query -/// the language setting as early in the boot process as possible. Specfically prior to any JSON files being -/// loaded such that JSON file can be translated. Also since this is a one-off mechanism custom build overrides -/// for language are not currently supported. +/** + * @brief 早期访问应用程序语言设置 + * + * 绕过标准的 SettingsGroup 路径获取当前应用程序语言设置,并验证该值是否为支持的语言。 + * 仅用于 QGCApplication::setLanguage 在启动过程中尽早查询语言设置,以便对 JSON 文件进行翻译。 + * @return 当前应用程序语言设置 + */ QLocale::Language AppSettings::_qLocaleLanguageEarlyAccess(void) { + // 创建 QSettings 对象,用于读写应用程序设置 QSettings settings; - // Note that the AppSettings group has no group name + // 注意 AppSettings 组没有组名,获取当前应用程序语言设置 QLocale::Language localeLanguage = static_cast(settings.value(qLocaleLanguageName).toInt()); + // 遍历语言信息数组,检查该语言是否为支持的语言 for (auto& languageInfo: _rgLanguageInfo) { if (languageInfo.languageId == localeLanguage) { return localeLanguage; } } + // 如果语言不支持,设置为系统默认语言并更新设置 localeLanguage = QLocale::AnyLanguage; settings.setValue(qLocaleLanguageName, localeLanguage); diff --git a/src/Settings/AppSettings.h b/src/Settings/AppSettings.h index 094e1c07462a8d0d7f55110d8a6c6dd2edd24fa5..ba2727bc9ca87e241d43e4b60c43f1c865c497ab 100644 --- a/src/Settings/AppSettings.h +++ b/src/Settings/AppSettings.h @@ -10,128 +10,236 @@ /// @file /// @brief Application Settings +// 确保头文件只被编译一次,防止重复包含 #pragma once +// 包含 SettingsGroup 类的头文件 #include "SettingsGroup.h" -/// Application Settings +/// @brief 应用程序设置类,继承自 SettingsGroup,用于管理应用程序的各种设置。 class AppSettings : public SettingsGroup { Q_OBJECT public: + /// @brief 构造函数 + /// @param parent 父对象,默认为 nullptr AppSettings(QObject* parent = nullptr); + /// @brief 定义设置名称组,具体实现由宏展开 DEFINE_SETTING_NAME_GROUP() + /// @brief 定义离线编辑固件类型设置项 DEFINE_SETTINGFACT(offlineEditingFirmwareClass) + /// @brief 定义离线编辑载具类型设置项 DEFINE_SETTINGFACT(offlineEditingVehicleClass) + /// @brief 定义离线编辑巡航速度设置项 DEFINE_SETTINGFACT(offlineEditingCruiseSpeed) + /// @brief 定义离线编辑悬停速度设置项 DEFINE_SETTINGFACT(offlineEditingHoverSpeed) + /// @brief 定义离线编辑上升速度设置项 DEFINE_SETTINGFACT(offlineEditingAscentSpeed) + /// @brief 定义离线编辑下降速度设置项 DEFINE_SETTINGFACT(offlineEditingDescentSpeed) - DEFINE_SETTINGFACT(batteryPercentRemainingAnnounce) // Important: This is only used to calculate battery swaps + /// @brief 定义电池剩余百分比通知设置项 + /// @note 此设置仅用于计算电池更换 + DEFINE_SETTINGFACT(batteryPercentRemainingAnnounce) + /// @brief 定义默认任务项高度设置项 DEFINE_SETTINGFACT(defaultMissionItemAltitude) + /// @brief 定义音频静音设置项 DEFINE_SETTINGFACT(audioMuted) + /// @brief 定义虚拟摇杆启用设置项 DEFINE_SETTINGFACT(virtualJoystick) + /// @brief 定义虚拟摇杆油门自动回中设置项 DEFINE_SETTINGFACT(virtualJoystickAutoCenterThrottle) + /// @brief 定义虚拟摇杆左手模式设置项 DEFINE_SETTINGFACT(virtualJoystickLeftHandedMode) + /// @brief 定义应用程序字体大小设置项 DEFINE_SETTINGFACT(appFontPointSize) + /// @brief 定义室内调色板设置项 DEFINE_SETTINGFACT(indoorPalette) + /// @brief 定义保存路径设置项 DEFINE_SETTINGFACT(savePath) + /// @brief 定义 Android 保存到 SD 卡设置项 DEFINE_SETTINGFACT(androidSaveToSDCard) + /// @brief 定义使用检查清单设置项 DEFINE_SETTINGFACT(useChecklist) + /// @brief 定义强制使用检查清单设置项 DEFINE_SETTINGFACT(enforceChecklist) + /// @brief 定义启用多载具面板设置项 DEFINE_SETTINGFACT(enableMultiVehiclePanel) + /// @brief 定义 Mapbox 令牌设置项 DEFINE_SETTINGFACT(mapboxToken) + /// @brief 定义 Mapbox 账户设置项 DEFINE_SETTINGFACT(mapboxAccount) + /// @brief 定义 Mapbox 样式设置项 DEFINE_SETTINGFACT(mapboxStyle) + /// @brief 定义 Esri 令牌设置项 DEFINE_SETTINGFACT(esriToken) + /// @brief 定义自定义 URL 设置项 DEFINE_SETTINGFACT(customURL) + /// @brief 定义 VWorld 令牌设置项 DEFINE_SETTINGFACT(vworldToken) + /// @brief 定义 GStreamer 调试级别设置项 DEFINE_SETTINGFACT(gstDebugLevel) + /// @brief 定义跟随目标设置项 DEFINE_SETTINGFACT(followTarget) + /// @brief 定义应用程序语言设置项 DEFINE_SETTINGFACT(qLocaleLanguage) + /// @brief 定义禁用所有持久化设置项 DEFINE_SETTINGFACT(disableAllPersistence) + /// @brief 定义首次运行提示 ID 显示设置项 DEFINE_SETTINGFACT(firstRunPromptIdsShown) + /// @brief 定义 AirLink 登录名设置项 DEFINE_SETTINGFACT(loginAirLink) + /// @brief 定义 AirLink 密码设置项 DEFINE_SETTINGFACT(passAirLink) + /// @brief 定义任务保存路径属性,通过 missionSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString missionSavePath READ missionSavePath NOTIFY savePathsChanged) + /// @brief 定义参数保存路径属性,通过 parameterSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString parameterSavePath READ parameterSavePath NOTIFY savePathsChanged) + /// @brief 定义遥测数据保存路径属性,通过 telemetrySavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString telemetrySavePath READ telemetrySavePath NOTIFY savePathsChanged) + /// @brief 定义日志保存路径属性,通过 logSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString logSavePath READ logSavePath NOTIFY savePathsChanged) + /// @brief 定义视频保存路径属性,通过 videoSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString videoSavePath READ videoSavePath NOTIFY savePathsChanged) + /// @brief 定义照片保存路径属性,通过 photoSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString photoSavePath READ photoSavePath NOTIFY savePathsChanged) + /// @brief 定义崩溃日志保存路径属性,通过 crashSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString crashSavePath READ crashSavePath NOTIFY savePathsChanged) + /// @brief 定义 Mavlink 动作保存路径属性,通过 mavlinkActionsSavePath 函数读取,保存路径改变时发出信号 Q_PROPERTY(QString mavlinkActionsSavePath READ mavlinkActionsSavePath NOTIFY savePathsChanged) + /// @brief 定义计划文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString planFileExtension MEMBER planFileExtension CONSTANT) + /// @brief 定义任务文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString missionFileExtension MEMBER missionFileExtension CONSTANT) + /// @brief 定义航点文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString waypointsFileExtension MEMBER waypointsFileExtension CONSTANT) + /// @brief 定义参数文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString parameterFileExtension MEMBER parameterFileExtension CONSTANT) + /// @brief 定义遥测数据文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString telemetryFileExtension MEMBER telemetryFileExtension CONSTANT) + /// @brief 定义 KML 文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString kmlFileExtension MEMBER kmlFileExtension CONSTANT) + /// @brief 定义 SHP 文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString shpFileExtension MEMBER shpFileExtension CONSTANT) + /// @brief 定义日志文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString logFileExtension MEMBER logFileExtension CONSTANT) + /// @brief 定义瓦片集文件扩展名属性,直接访问成员变量,为常量 Q_PROPERTY(QString tilesetFileExtension MEMBER tilesetFileExtension CONSTANT) + /// @brief 获取任务保存路径 + /// @return 任务保存路径字符串 QString missionSavePath (); + /// @brief 获取参数保存路径 + /// @return 参数保存路径字符串 QString parameterSavePath (); + /// @brief 获取遥测数据保存路径 + /// @return 遥测数据保存路径字符串 QString telemetrySavePath (); + /// @brief 获取日志保存路径 + /// @return 日志保存路径字符串 QString logSavePath (); + /// @brief 获取视频保存路径 + /// @return 视频保存路径字符串 QString videoSavePath (); + /// @brief 获取照片保存路径 + /// @return 照片保存路径字符串 QString photoSavePath (); + /// @brief 获取崩溃日志保存路径 + /// @return 崩溃日志保存路径字符串 QString crashSavePath (); + /// @brief 获取 Mavlink 动作保存路径 + /// @return Mavlink 动作保存路径字符串 QString mavlinkActionsSavePath (); - // Helper methods for working with firstRunPromptIds QVariant settings string list + /// @brief 将首次运行提示 ID 的 QVariant 字符串列表转换为整数列表 + /// @param firstRunPromptIds 首次运行提示 ID 的 QVariant 字符串列表 + /// @return 转换后的整数列表 static QList firstRunPromptsIdsVariantToList (const QVariant& firstRunPromptIds); + /// @brief 将首次运行提示 ID 的整数列表转换为 QVariant 字符串列表 + /// @param rgIds 首次运行提示 ID 的整数列表 + /// @return 转换后的 QVariant 字符串列表 static QVariant firstRunPromptsIdsListToVariant (const QList& rgIds); + /// @brief 标记指定的首次运行提示 ID 为已显示 + /// @param id 要标记的首次运行提示 ID Q_INVOKABLE void firstRunPromptIdsMarkIdAsShown (int id); - // Application wide file extensions + // 应用程序范围内的文件扩展名 + /// @brief 参数文件扩展名 static constexpr const char* parameterFileExtension = "params"; + /// @brief 计划文件扩展名 static constexpr const char* planFileExtension = "plan"; + /// @brief 任务文件扩展名 static constexpr const char* missionFileExtension = "mission"; + /// @brief 航点文件扩展名 static constexpr const char* waypointsFileExtension = "waypoints"; + /// @brief 围栏文件扩展名 static constexpr const char* fenceFileExtension = "fence"; + /// @brief 集结点文件扩展名 static constexpr const char* rallyPointFileExtension = "rally"; + /// @brief 遥测数据文件扩展名 static constexpr const char* telemetryFileExtension = "tlog"; + /// @brief KML 文件扩展名 static constexpr const char* kmlFileExtension = "kml"; + /// @brief SHP 文件扩展名 static constexpr const char* shpFileExtension = "shp"; + /// @brief 日志文件扩展名 static constexpr const char* logFileExtension = "ulg"; + /// @brief 瓦片集文件扩展名 static constexpr const char* tilesetFileExtension = "qgctiledb"; - // Child directories of savePath for specific file types + // 保存路径下特定文件类型的子目录 + /// @brief 参数文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* parameterDirectory = QT_TRANSLATE_NOOP("AppSettings", "Parameters"); + /// @brief 遥测数据文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* telemetryDirectory = QT_TRANSLATE_NOOP("AppSettings", "Telemetry"); + /// @brief 任务文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* missionDirectory = QT_TRANSLATE_NOOP("AppSettings", "Missions"); + /// @brief 日志文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* logDirectory = QT_TRANSLATE_NOOP("AppSettings", "Logs"); + /// @brief 视频文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* videoDirectory = QT_TRANSLATE_NOOP("AppSettings", "Video"); + /// @brief 照片文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* photoDirectory = QT_TRANSLATE_NOOP("AppSettings", "Photo"); + /// @brief 崩溃日志文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* crashDirectory = QT_TRANSLATE_NOOP("AppSettings", "CrashLogs"); + /// @brief Mavlink 动作文件子目录名称,使用翻译宏,可进行多语言翻译 static constexpr const char* mavlinkActionsDirectory = QT_TRANSLATE_NOOP("AppSettings", "MavlinkActions"); signals: + /// @brief 保存路径改变时发出的信号 void savePathsChanged(); private slots: + /// @brief 室内调色板设置改变时的槽函数 void _indoorPaletteChanged(); + /// @brief 检查保存路径目录是否存在的槽函数 void _checkSavePathDirectories(); + /// @brief 应用程序语言设置改变时的槽函数 void _qLocaleLanguageChanged(); private: + /// @brief 获取早期访问的应用程序语言 + /// @return 早期访问的应用程序语言枚举值 static QLocale::Language _qLocaleLanguageEarlyAccess(void); + /// @brief 已发布语言列表 static QList _rgReleaseLanguages; + /// @brief 部分支持语言列表 static QList _rgPartialLanguages; + /// @brief 语言信息结构体,包含语言 ID 和语言名称 typedef struct { - QLocale::Language languageId; - const char* languageName; + QLocale::Language languageId; // 语言 ID + const char* languageName; // 语言名称 } LanguageInfo_t; + /// @brief 语言信息数组 static LanguageInfo_t _rgLanguageInfo[]; + /// 声明 QGCApplication 为友元类,使其可以访问私有成员 friend class QGCApplication; }; diff --git a/src/Settings/SettingsGroup.h b/src/Settings/SettingsGroup.h index 5c43d720539fd7c96ba472cc33a29b26bdaa9f3e..047ef0af5d974bfcf01c18475e50d0a4b07fdc2d 100644 --- a/src/Settings/SettingsGroup.h +++ b/src/Settings/SettingsGroup.h @@ -7,21 +7,44 @@ * ****************************************************************************/ +// 确保头文件只被编译一次,防止重复包含 #pragma once - +// 包含 SettingsFact 类的头文件,用于处理设置项 #include "SettingsFact.h" +/** + * @brief 定义设置组的名称和 QSettings 组名 + * + * 该宏用于在设置组类中声明静态成员变量 name 和 settingsGroup, + * 分别表示设置组的名称和在 QSettings 中使用的组名。 + */ #define DEFINE_SETTING_NAME_GROUP() \ static const char* name; \ static const char* settingsGroup; +/** + * @brief 声明设置组类 + * + * 该宏用于声明设置组类的静态成员变量 name 和 settingsGroup 的定义, + * 并定义设置组类的构造函数。 + * @param NAME 设置组类的名称 + * @param GROUP 在 QSettings 中使用的组名 + */ #define DECLARE_SETTINGGROUP(NAME, GROUP) \ const char* NAME ## Settings::name = #NAME; \ const char* NAME ## Settings::settingsGroup = GROUP; \ NAME ## Settings::NAME ## Settings(QObject* parent) \ : SettingsGroup(name, settingsGroup, parent) +/** + * @brief 声明设置项事实 + * + * 该宏用于声明设置项事实的名称和访问函数, + * 确保设置项事实对象只被创建一次。 + * @param CLASS 设置组类的名称 + * @param NAME 设置项事实的名称 + */ #define DECLARE_SETTINGSFACT(CLASS, NAME) \ const char* CLASS::NAME ## Name = #NAME; \ Fact* CLASS::NAME() \ @@ -32,10 +55,23 @@ return _ ## NAME ## Fact; \ } +/** + * @brief 声明设置项事实(无函数实现) + * + * 该宏用于声明设置项事实的名称和访问函数,但不包含函数实现。 + * @param CLASS 设置组类的名称 + * @param NAME 设置项事实的名称 + */ #define DECLARE_SETTINGSFACT_NO_FUNC(CLASS, NAME) \ const char* CLASS::NAME ## Name = #NAME; \ Fact* CLASS::NAME() +/** + * @brief 定义设置项事实 + * + * 该宏用于在设置组类中定义设置项事实的私有成员变量、Qt 属性和访问函数。 + * @param NAME 设置项事实的名称 + */ #define DEFINE_SETTINGFACT(NAME) \ private: \ SettingsFact* _ ## NAME ## Fact = nullptr; \ @@ -44,33 +80,59 @@ Fact* NAME(); \ static const char* NAME ## Name; -/// Provides access to group of settings. The group is named and has a visible property associated with which can control whether the group -/// is shows in the ui. +/// @brief 提供对一组设置的访问。该组有名称和可见性属性,可控制该组是否在 UI 中显示。 class SettingsGroup : public QObject { Q_OBJECT public: - /// @param name Name for this Settings group - /// @param settingsGroup Group to place settings in for QSettings::setGroup + /** + * @brief 构造函数 + * + * @param name 此设置组的名称 + * @param settingsGroup 用于 QSettings::setGroup 的设置组名 + * @param parent 父对象,默认为 nullptr + */ SettingsGroup(const QString &name, const QString &settingsGroup, QObject* parent = nullptr); + // 定义 Qt 属性,用于获取和设置设置组的可见性,可见性改变时发出信号 Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged) + /** + * @brief 获取设置组的可见性 + * + * @return 设置组的可见性状态,true 表示可见,false 表示不可见 + */ virtual bool visible () { return _visible; } + + /** + * @brief 设置设置组的可见性 + * + * @param vis 要设置的可见性状态,true 表示可见,false 表示不可见 + */ virtual void setVisible (bool vis) { _visible = vis; emit visibleChanged(); } signals: + /// @brief 当设置组的可见性发生改变时发出此信号 void visibleChanged (); protected: + /** + * @brief 创建设置项事实对象 + * + * @param factName 设置项事实的名称 + * @return 指向创建的 SettingsFact 对象的指针 + */ SettingsFact* _createSettingsFact(const QString& factName); - bool _visible; - QString _name; - QString _settingsGroup; + bool _visible; // 设置组的可见性状态 + QString _name; // 设置组的名称 + QString _settingsGroup; // 用于 QSettings::setGroup 的设置组名 + + // 名称到元数据的映射,用于存储每个设置项事实的元数据 QMap _nameToMetaDataMap; private: + // 用于加载设置组元数据的 JSON 文件路径模板,%1 会被替换为设置组的名称 static constexpr const char* kJsonFile = ":/json/%1.SettingsGroup.json"; }; diff --git a/src/Settings/SettingsManager.cc b/src/Settings/SettingsManager.cc index 73e4051292ac098a4eae3ace135ba1895a5e3346..bf3384cadb20671db26e260617729eec3c562c11 100644 --- a/src/Settings/SettingsManager.cc +++ b/src/Settings/SettingsManager.cc @@ -7,70 +7,125 @@ * ****************************************************************************/ +// 包含 SettingsManager 类的头文件 #include "SettingsManager.h" +// 包含 QGC 日志分类的头文件,用于日志记录 #include "QGCLoggingCategory.h" +// 包含 ADSB 车辆管理设置类的头文件 #include "ADSBVehicleManagerSettings.h" +// 如果未定义 QGC_NO_ARDUPILOT_DIALECT 宏,则包含 ArduPilot Mavlink 流速率设置类的头文件 #ifndef QGC_NO_ARDUPILOT_DIALECT #include "APMMavlinkStreamRateSettings.h" #endif +// 包含应用设置类的头文件 #include "AppSettings.h" +// 包含自动连接设置类的头文件 #include "AutoConnectSettings.h" +// 包含电池指示器设置类的头文件 #include "BatteryIndicatorSettings.h" +// 包含品牌图像设置类的头文件 #include "BrandImageSettings.h" +// 包含 Mavlink 动作设置类的头文件 #include "MavlinkActionsSettings.h" +// 包含固件升级设置类的头文件 #include "FirmwareUpgradeSettings.h" +// 包含飞行地图设置类的头文件 #include "FlightMapSettings.h" +// 包含飞行模式设置类的头文件 #include "FlightModeSettings.h" +// 包含飞行视图设置类的头文件 #include "FlyViewSettings.h" +// 包含云台控制器设置类的头文件 #include "GimbalControllerSettings.h" +// 包含地图设置类的头文件 #include "MapsSettings.h" +// 包含离线地图设置类的头文件 #include "OfflineMapsSettings.h" +// 包含计划视图设置类的头文件 #include "PlanViewSettings.h" +// 包含远程 ID 设置类的头文件 #include "RemoteIDSettings.h" +// 包含 RTK 设置类的头文件 #include "RTKSettings.h" +// 包含单位设置类的头文件 #include "UnitsSettings.h" +// 包含视频设置类的头文件 #include "VideoSettings.h" +// 包含 Mavlink 设置类的头文件 #include "MavlinkSettings.h" +// 如果定义了 QGC_VIEWER3D 宏,则包含 3D 视图设置类的头文件 #ifdef QGC_VIEWER3D #include "Viewer3DSettings.h" #endif +// 包含 Qt 应用程序静态对象相关的头文件 #include +// 包含 Qt QML 相关的头文件,用于 QML 类型注册 #include +// 定义一个日志分类,方便对 SettingsManager 相关的日志进行管理和过滤 QGC_LOGGING_CATEGORY(SettingsManagerLog, "qgc.settings.settingsmanager") +// 使用 Q_APPLICATION_STATIC 宏创建一个 SettingsManager 类的静态实例 Q_APPLICATION_STATIC(SettingsManager, _settingsManagerInstance); +/** + * @brief SettingsManager 类的构造函数 + * + * @param parent 父对象指针,默认为 nullptr + */ SettingsManager::SettingsManager(QObject *parent) : QObject(parent) { + // 注释掉的调试日志,可用于输出构造函数调用信息和对象指针 // qCDebug(SettingsManagerLog) << Q_FUNC_INFO << this; } +/** + * @brief SettingsManager 类的析构函数 + */ SettingsManager::~SettingsManager() { + // 注释掉的调试日志,可用于输出析构函数调用信息和对象指针 // qCDebug(SettingsManagerLog) << Q_FUNC_INFO << this; } +/** + * @brief 获取 SettingsManager 类的单例实例 + * + * @return SettingsManager* SettingsManager 类的单例实例指针 + */ SettingsManager *SettingsManager::instance() { return _settingsManagerInstance(); } +/** + * @brief 注册 SettingsManager 类为 QML 类型 + * + * 将 SettingsManager 类注册为 QML 类型,但不可在 QML 中直接创建实例,仅作引用使用 + */ void SettingsManager::registerQmlTypes() { + // 注册 SettingsManager 为 QML 类型,不可创建实例,仅作引用 (void) qmlRegisterUncreatableType("QGroundControl.SettingsManager", 1, 0, "SettingsManager", "Reference only"); } +/** + * @brief 初始化 SettingsManager 类的各个设置对象 + * + * 依次创建并初始化各类设置对象,注意 UnitsSettings 需首先创建,因为 AppSettings 会引用它 + */ void SettingsManager::init() { - _unitsSettings = new UnitsSettings(this); // Must be first since AppSettings references it + // 必须首先创建 UnitsSettings,因为 AppSettings 会引用它 + _unitsSettings = new UnitsSettings(this); _adsbVehicleManagerSettings = new ADSBVehicleManagerSettings(this); -#ifndef QGC_NO_ARDUPILOT_DIALECT + // 如果未定义 QGC_NO_ARDUPILOT_DIALECT 宏,则创建 ArduPilot Mavlink 流速率设置对象 + #ifndef QGC_NO_ARDUPILOT_DIALECT _apmMavlinkStreamRateSettings = new APMMavlinkStreamRateSettings(this); -#endif + #endif _appSettings = new AppSettings(this); _autoConnectSettings = new AutoConnectSettings(this); _batteryIndicatorSettings = new BatteryIndicatorSettings(this); @@ -88,33 +143,141 @@ void SettingsManager::init() _rtkSettings = new RTKSettings(this); _videoSettings = new VideoSettings(this); _mavlinkSettings = new MavlinkSettings(this); -#ifdef QGC_VIEWER3D + // 如果定义了 QGC_VIEWER3D 宏,则创建 3D 视图设置对象 + #ifdef QGC_VIEWER3D _viewer3DSettings = new Viewer3DSettings(this); -#endif + #endif } +/** + * @brief 获取 ADSB 车辆管理设置对象指针 + * + * @return ADSBVehicleManagerSettings* ADSB 车辆管理设置对象指针 + */ ADSBVehicleManagerSettings *SettingsManager::adsbVehicleManagerSettings() const { return _adsbVehicleManagerSettings; } +// 如果未定义 QGC_NO_ARDUPILOT_DIALECT 宏,提供获取 ArduPilot Mavlink 流速率设置对象指针的方法 #ifndef QGC_NO_ARDUPILOT_DIALECT +/** + * @brief 获取 ArduPilot Mavlink 流速率设置对象指针 + * + * @return APMMavlinkStreamRateSettings* ArduPilot Mavlink 流速率设置对象指针 + */ APMMavlinkStreamRateSettings *SettingsManager::apmMavlinkStreamRateSettings() const { return _apmMavlinkStreamRateSettings; } #endif +/** + * @brief 获取应用设置对象指针 + * + * @return AppSettings* 应用设置对象指针 + */ AppSettings *SettingsManager::appSettings() const { return _appSettings; } +/** + * @brief 获取自动连接设置对象指针 + * + * @return AutoConnectSettings* 自动连接设置对象指针 + */ AutoConnectSettings *SettingsManager::autoConnectSettings() const { return _autoConnectSettings; } +/** + * @brief 获取电池指示器设置对象指针 + * + * @return BatteryIndicatorSettings* 电池指示器设置对象指针 + */ BatteryIndicatorSettings *SettingsManager::batteryIndicatorSettings() const { return _batteryIndicatorSettings; } +/** + * @brief 获取品牌图像设置对象指针 + * + * @return BrandImageSettings* 品牌图像设置对象指针 + */ BrandImageSettings *SettingsManager::brandImageSettings() const { return _brandImageSettings; } +/** + * @brief 获取 Mavlink 动作设置对象指针 + * + * @return MavlinkActionsSettings* Mavlink 动作设置对象指针 + */ MavlinkActionsSettings *SettingsManager::mavlinkActionsSettings() const { return _mavlinkActionsSettings; } +/** + * @brief 获取固件升级设置对象指针 + * + * @return FirmwareUpgradeSettings* 固件升级设置对象指针 + */ FirmwareUpgradeSettings *SettingsManager::firmwareUpgradeSettings() const { return _firmwareUpgradeSettings; } +/** + * @brief 获取飞行地图设置对象指针 + * + * @return FlightMapSettings* 飞行地图设置对象指针 + */ FlightMapSettings *SettingsManager::flightMapSettings() const { return _flightMapSettings; } +/** + * @brief 获取飞行模式设置对象指针 + * + * @return FlightModeSettings* 飞行模式设置对象指针 + */ FlightModeSettings *SettingsManager::flightModeSettings() const { return _flightModeSettings; } +/** + * @brief 获取飞行视图设置对象指针 + * + * @return FlyViewSettings* 飞行视图设置对象指针 + */ FlyViewSettings *SettingsManager::flyViewSettings() const { return _flyViewSettings; } +/** + * @brief 获取云台控制器设置对象指针 + * + * @return GimbalControllerSettings* 云台控制器设置对象指针 + */ GimbalControllerSettings *SettingsManager::gimbalControllerSettings() const { return _gimbalControllerSettings; } +/** + * @brief 获取地图设置对象指针 + * + * @return MapsSettings* 地图设置对象指针 + */ MapsSettings *SettingsManager::mapsSettings() const { return _mapsSettings; } +/** + * @brief 获取离线地图设置对象指针 + * + * @return OfflineMapsSettings* 离线地图设置对象指针 + */ OfflineMapsSettings *SettingsManager::offlineMapsSettings() const { return _offlineMapsSettings; } +/** + * @brief 获取计划视图设置对象指针 + * + * @return PlanViewSettings* 计划视图设置对象指针 + */ PlanViewSettings *SettingsManager::planViewSettings() const { return _planViewSettings; } +/** + * @brief 获取远程 ID 设置对象指针 + * + * @return RemoteIDSettings* 远程 ID 设置对象指针 + */ RemoteIDSettings *SettingsManager::remoteIDSettings() const { return _remoteIDSettings; } +/** + * @brief 获取 RTK 设置对象指针 + * + * @return RTKSettings* RTK 设置对象指针 + */ RTKSettings *SettingsManager::rtkSettings() const { return _rtkSettings; } +/** + * @brief 获取单位设置对象指针 + * + * @return UnitsSettings* 单位设置对象指针 + */ UnitsSettings *SettingsManager::unitsSettings() const { return _unitsSettings; } +/** + * @brief 获取视频设置对象指针 + * + * @return VideoSettings* 视频设置对象指针 + */ VideoSettings *SettingsManager::videoSettings() const { return _videoSettings; } +/** + * @brief 获取 Mavlink 设置对象指针 + * + * @return MavlinkSettings* Mavlink 设置对象指针 + */ MavlinkSettings *SettingsManager::mavlinkSettings() const { return _mavlinkSettings; } +// 如果定义了 QGC_VIEWER3D 宏,提供获取 3D 视图设置对象指针的方法 #ifdef QGC_VIEWER3D +/** + * @brief 获取 3D 视图设置对象指针 + * + * @return Viewer3DSettings* 3D 视图设置对象指针 + */ Viewer3DSettings *SettingsManager::viewer3DSettings() const { return _viewer3DSettings; } #endif diff --git a/src/Settings/SettingsManager.h b/src/Settings/SettingsManager.h index 5ff9918edee6efdbd35e5f57897602dbc2f8d0d6..e60a41aafe4da4335f0d9e692eafdd1928ce307e 100644 --- a/src/Settings/SettingsManager.h +++ b/src/Settings/SettingsManager.h @@ -7,12 +7,17 @@ * ****************************************************************************/ +// 防止头文件被重复包含 #pragma once +// 包含 Qt 日志类别相关的头文件,用于日志系统 #include +// 包含 Qt 对象基类的头文件,QObject 是 Qt 元对象系统的核心类 #include +// 包含 Qt QML 集成相关的头文件,用于将 C++ 类集成到 QML 环境中 #include +// 前向声明各类设置类,告知编译器这些类的存在,减少头文件依赖 class ADSBVehicleManagerSettings; class APMMavlinkStreamRateSettings; class AppSettings; @@ -35,14 +40,20 @@ class VideoSettings; class Viewer3DSettings; class MavlinkSettings; +// 声明一个日志类别,用于在日志系统中对 SettingsManager 相关的日志进行分类 Q_DECLARE_LOGGING_CATEGORY(SettingsManagerLog) /// Provides access to all app settings +// SettingsManager 类,继承自 QObject,用于管理应用程序的所有设置 class SettingsManager : public QObject { - Q_OBJECT - // QML_ELEMENT - // QML_UNCREATABLE("") + Q_OBJECT // 启用 Qt 元对象系统,支持信号与槽、属性系统等功能 + + // 以下注释的宏可用于将该类注册为 QML 类型 + // QML_ELEMENT // 注册为可在 QML 中创建的类型 + // QML_UNCREATABLE("") // 声明该类型不可在 QML registerQmlTypes中直接创建 + + // 告知 MOC 显式包含指定的头文件,确保元对象系统能正确处理相关类 Q_MOC_INCLUDE("ADSBVehicleManagerSettings.h") #ifndef QGC_NO_ARDUPILOT_DIALECT Q_MOC_INCLUDE("APMMavlinkStreamRateSettings.h") @@ -68,6 +79,8 @@ class SettingsManager : public QObject #ifdef QGC_VIEWER3D Q_MOC_INCLUDE("Viewer3DSettings.h") #endif + + // 定义 QML 属性,用于在 QML 中访问各类设置对象 Q_PROPERTY(QObject *adsbVehicleManagerSettings READ adsbVehicleManagerSettings CONSTANT) #ifndef QGC_NO_ARDUPILOT_DIALECT Q_PROPERTY(QObject *apmMavlinkStreamRateSettings READ apmMavlinkStreamRateSettings CONSTANT) @@ -94,14 +107,20 @@ class SettingsManager : public QObject Q_PROPERTY(QObject *viewer3DSettings READ viewer3DSettings CONSTANT) #endif public: + // 构造函数,parent 为父对象,默认为 nullptr SettingsManager(QObject *parent = nullptr); + // 析构函数 ~SettingsManager(); + // 获取 SettingsManager 单例实例的静态方法 static SettingsManager *instance(); + // 注册 QML 类型的静态方法 static void registerQmlTypes(); + // 初始化 SettingsManager 的方法 void init(); + // 获取各类设置对象的方法 ADSBVehicleManagerSettings *adsbVehicleManagerSettings() const; #ifndef QGC_NO_ARDUPILOT_DIALECT APMMavlinkStreamRateSettings *apmMavlinkStreamRateSettings() const; @@ -129,6 +148,7 @@ class SettingsManager : public QObject #endif private: + // 指向各类设置对象的私有成员指针,初始化为 nullptr ADSBVehicleManagerSettings *_adsbVehicleManagerSettings = nullptr; #ifndef QGC_NO_ARDUPILOT_DIALECT APMMavlinkStreamRateSettings *_apmMavlinkStreamRateSettings = nullptr; diff --git a/src/main.cc b/src/main.cc index b599df10669182dfd05f75c2271e86b9ba06a4e6..e29af81ddd5a4dc26d7132ddf31945ffe40bf7ed 100644 --- a/src/main.cc +++ b/src/main.cc @@ -7,54 +7,82 @@ * ****************************************************************************/ +// 包含 Qt Quick 窗口模块头文件,用于创建和管理 Qt Quick 窗口 #include +// 包含 Qt Widgets 应用程序模块头文件,用于创建和管理 Qt Widgets 应用程序 #include +// 如果是 macOS 系统,包含 Qt 进程环境模块头文件 #ifdef Q_OS_MACOS #include #endif +// 包含 QGroundControl 应用程序头文件 #include "QGCApplication.h" +// 包含 QGroundControl 日志模块头文件 #include "QGCLogging.h" +// 包含命令行选项解析器头文件 #include "CmdLineOptParser.h" +// 包含设置管理器头文件 #include "SettingsManager.h" +// 包含 Mavlink 设置头文件 #include "MavlinkSettings.h" +// 如果不是 Android 和 iOS 系统 #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + // 包含 Qt Widgets 消息框模块头文件,用于显示消息框 #include + // 包含运行保护头文件,用于防止应用多开 #include "RunGuard.h" #endif +// 如果是 Android 系统,包含 Android 接口头文件 #ifdef Q_OS_ANDROID #include "AndroidInterface.h" #endif +// 如果是 Linux 系统且不是 Android 系统 #ifdef Q_OS_LINUX #ifndef Q_OS_ANDROID + // 包含信号处理头文件,用于处理系统信号 #include "SignalHandler.h" #endif #endif +// 如果是调试模式 #ifdef QT_DEBUG +// 如果是单元测试构建 #ifdef QGC_UNITTEST_BUILD + // 包含单元测试列表头文件 #include "UnitTestList.h" #endif +// 如果是 Windows 系统 #ifdef Q_OS_WIN +// 包含 CRT 调试头文件,用于内存调试 #include +// 包含 Windows API 头文件 #include +// 包含标准输入输出流头文件 #include -/// @brief CRT Report Hook installed using _CrtSetReportHook. We install this hook when -/// we don't want asserts to pop a dialog on windows. +/// @brief CRT 报告钩子函数,使用 _CrtSetReportHook 安装。当不想让 Windows 上的断言弹出对话框时安装此钩子。 +/// @param reportType 报告类型 +/// @param message 报告消息 +/// @param returnValue 返回值指针 +/// @return 若完全处理该报告则返回 true,否则返回 false int WindowsCrtReportHook(int reportType, char* message, int* returnValue) { + // 忽略报告类型参数 Q_UNUSED(reportType); - std::cerr << message << std::endl; // Output message to stderr - *returnValue = 0; // Don't break into debugger - return true; // We handled this fully ourselves + // 将消息输出到标准错误流 + std::cerr << message << std::endl; + // 不进入调试器 + *returnValue = 0; + // 表示已完全处理该报告 + return true; } #endif // Q_OS_WIN @@ -63,167 +91,233 @@ int WindowsCrtReportHook(int reportType, char* message, int* returnValue) //----------------------------------------------------------------------------- /** - * @brief Starts the application + * @brief 启动应用程序 * - * @param argc Number of commandline arguments - * @param argv Commandline arguments - * @return exit code, 0 for normal exit and !=0 for error cases + * @param argc 命令行参数数量 + * @param argv 命令行参数数组 + * @return 退出码,正常退出返回 0,出错返回非 0 值 */ int main(int argc, char *argv[]) { + // 是否运行单元测试标志 bool runUnitTests = false; + // 是否进行简单启动测试标志 bool simpleBootTest = false; + // 系统 ID 字符串 QString systemIdStr = QString(); + // 是否指定了系统 ID 标志 bool hasSystemId = false; + // 是否绕过运行保护标志 bool bypassRunGuard = false; - bool stressUnitTests = false; // Stress test unit tests - bool quietWindowsAsserts = false; // Don't let asserts pop dialog boxes + // 是否进行单元测试压力测试标志 + bool stressUnitTests = false; + // 是否禁止 Windows 断言弹出对话框标志 + bool quietWindowsAsserts = false; + // 单元测试选项字符串 QString unitTestOptions; + // 命令行选项数组 CmdLineOpt_t rgCmdLineOptions[] = { #ifdef QT_DEBUG + // 运行单元测试选项 { "--unittest", &runUnitTests, &unitTestOptions }, + // 运行单元测试压力测试选项 { "--unittest-stress", &stressUnitTests, &unitTestOptions }, + // 禁止 Windows 断言弹出对话框选项 { "--no-windows-assert-ui", &quietWindowsAsserts, nullptr }, + // 绕过运行保护选项 { "--allow-multiple", &bypassRunGuard, nullptr }, #endif + // 指定系统 ID 选项 { "--system-id", &hasSystemId, &systemIdStr }, + // 进行简单启动测试选项 { "--simple-boot-test", &simpleBootTest, nullptr }, - // Add additional command line option flags here + // 可在此处添加额外的命令行选项标志 }; + // 解析命令行选项 ParseCmdLineOptions(argc, argv, rgCmdLineOptions, std::size(rgCmdLineOptions), false); + // 如果不是 Android 和 iOS 系统 #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - // We make the runguard key different for custom and non custom - // builds, so they can be executed together in the same device. - // Stable and Daily have same QGC_APP_NAME so they would - // not be able to run at the same time + // 为自定义和非自定义构建设置不同的运行保护键,以便在同一设备上同时运行 + // 稳定版和每日版的 QGC_APP_NAME 相同,因此它们不能同时运行 const QString runguardString = QStringLiteral("%1 RunGuardKey").arg(QGC_APP_NAME); + // 创建运行保护对象 RunGuard guard(runguardString); + // 如果不绕过运行保护且无法启动新实例 if (!bypassRunGuard && !guard.tryToRun()) { + // 创建错误处理应用程序对象 QApplication errorApp(argc, argv); + // 显示错误消息框 QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("A second instance of %1 is already running. Please close the other instance and try again.").arg(QGC_APP_NAME) ); + // 返回错误退出码 return -1; } #endif + // 如果是 Linux 系统且不是 Android 系统 #ifdef Q_OS_LINUX #ifndef Q_OS_ANDROID + // 如果以 root 用户运行 if (getuid() == 0) { + // 创建错误处理应用程序对象 QApplication errorApp(argc, argv); + // 显示错误消息框 QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("You are running %1 as root. " "You should not do this since it will cause other issues with %1." "%1 will now exit.

").arg(QGC_APP_NAME) ); + // 返回错误退出码 return -1; } #endif #endif + // 如果是 Unix 系统 #ifdef Q_OS_UNIX + // 如果未设置 QT_LOGGING_TO_CONSOLE 环境变量 if (!qEnvironmentVariableIsSet("QT_LOGGING_TO_CONSOLE")) { + // 设置 QT_LOGGING_TO_CONSOLE 环境变量为 1,将日志输出到控制台 qputenv("QT_LOGGING_TO_CONSOLE", "1"); } #endif + // 安装 QGroundControl 日志处理程序 QGCLogging::installHandler(); + // 如果是 macOS 系统 #ifdef Q_OS_MACOS - // Prevent Apple's app nap from screwing us over - // tip: the domain can be cross-checked on the command line with + // 防止 Apple 的应用休眠功能影响应用 + // 提示:可以在命令行使用 检查域 QProcess::execute("defaults", {"write org.qgroundcontrol.qgroundcontrol NSAppSleepDisabled -bool YES"}); #endif + // 如果是 Windows 系统 #ifdef Q_OS_WIN - // Set our own OpenGL buglist + // 设置自定义的 OpenGL 错误列表 // qputenv("QT_OPENGL_BUGLIST", ":/opengl/resources/opengl/buglist.json"); - // Allow for command line override of renderer + // 允许通过命令行覆盖渲染器 for (int i = 0; i < argc; i++) { + // 获取当前命令行参数 const QString arg(argv[i]); + // 如果参数为 -desktop if (arg == QStringLiteral("-desktop")) { + // 设置使用桌面 OpenGL QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); break; - } else if (arg == QStringLiteral("-swrast")) { + } + // 如果参数为 -swrast + else if (arg == QStringLiteral("-swrast")) { + // 设置使用软件 OpenGL QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); break; } } #endif + // 如果是调试模式 #ifdef QT_DEBUG + // 如果进行单元测试压力测试,则设置运行单元测试标志 if (stressUnitTests) { runUnitTests = true; } + // 如果是 Windows 系统 #ifdef Q_OS_WIN + // 如果未设置 QT_WIN_DEBUG_CONSOLE 环境变量 if (!qEnvironmentVariableIsSet("QT_WIN_DEBUG_CONSOLE")) { + // 设置 QT_WIN_DEBUG_CONSOLE 环境变量为 attach qputenv("QT_WIN_DEBUG_CONSOLE", "attach"); // new } + // 如果禁止 Windows 断言弹出对话框 if (quietWindowsAsserts) { + // 安装 CRT 报告钩子函数 _CrtSetReportHook(WindowsCrtReportHook); } + // 如果运行单元测试 if (runUnitTests) { - // Don't pop up Windows Error Reporting dialog when app crashes. This prevents TeamCity from - // hanging. + // 禁止 Windows 错误报告对话框弹出,防止 TeamCity 挂起 const DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX); SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX); } #endif // Q_OS_WIN #endif // QT_DEBUG + // 创建 QGroundControl 应用程序对象 QGCApplication app(argc, argv, runUnitTests, simpleBootTest); + // 如果是 Linux 系统且不是 Android 系统 #ifdef Q_OS_LINUX #ifndef Q_OS_ANDROID + // 获取信号处理单例对象 SignalHandler::instance(); + // 设置信号处理函数 (void) SignalHandler::setupSignalHandlers(); #endif #endif + // 初始化应用程序 app.init(); - // Set system ID if specified via command line, for example --system-id:255 + // 如果通过命令行指定了系统 ID,例如 --system-id:255 if (hasSystemId) { + // 转换标志 bool ok; + // 将系统 ID 字符串转换为整数 int systemId = systemIdStr.toInt(&ok); - if (ok && systemId >= 1 && systemId <= 255) { // MAVLink system IDs are 8-bit + // 如果转换成功且系统 ID 在有效范围内(MAVLink 系统 ID 为 8 位) + if (ok && systemId >= 1 && systemId <= 255) { + // 输出调试信息 qDebug() << "Setting MAVLink System ID to:" << systemId; + // 设置 MAVLink 系统 ID SettingsManager::instance()->mavlinkSettings()->gcsMavlinkSystemID()->setRawValue(systemId); } else { + // 输出调试信息,提示系统 ID 无效 qDebug() << "Not setting MAVLink System ID. It must be between 0 and 255. Invalid system ID value:" << systemIdStr; } } + // 退出码 int exitCode = 0; + // 如果是单元测试构建 #ifdef QGC_UNITTEST_BUILD + // 如果运行单元测试 if (runUnitTests) { + // 运行测试并获取退出码 exitCode = runTests(stressUnitTests, unitTestOptions); } else #endif { + // 如果是 Android 系统 #ifdef Q_OS_ANDROID + // 检查存储权限 AndroidInterface::checkStoragePermissions(); #endif + // 如果不进行简单启动测试 if (!simpleBootTest) { + // 运行应用程序并获取退出码 exitCode = app.exec(); } } + // 关闭应用程序 app.shutdown(); + // 输出调试信息,表示即将退出 main 函数 qDebug() << "Exiting main"; + // 返回退出码 return exitCode; }