Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f277d013 | ||
|
|
806aeb4824 | ||
|
|
d48abfba79 | ||
|
|
03d2e94f8b | ||
|
|
25fc14d6bd | ||
|
|
16787ac642 | ||
|
|
e64921702b | ||
|
|
f25c5789ea | ||
|
|
b5d0fef4d9 | ||
|
|
0601326e2b |
215
CMakeLists.txt
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
cmake_minimum_required (VERSION 3.20)
|
||||||
|
|
||||||
|
project ("kwa.fr")
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 23)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF) # Ensure only standard features are enabled (forces ISO C23, not GNU23)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF) # Ensure only standard features are enabled (forces ISO C++23, not GNU++23)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
string(APPEND CMAKE_C_FLAGS " /W3 /GR")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS " /W3 /GR")
|
||||||
|
|
||||||
|
string(APPEND CMAKE_C_FLAGS_DEBUG " /MDd /RTC1")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_DEBUG " /MDd /RTC1")
|
||||||
|
|
||||||
|
string(APPEND CMAKE_C_FLAGS_RELEASE " /MD")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /MD")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Set default build type to Release if not specified
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (Debug or Release)" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Global library environment variable
|
||||||
|
if(DEFINED ENV{DEV_LIB})
|
||||||
|
set(DEV_LIB $ENV{DEV_LIB})
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "DEV_LIB environment variable must be defined")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Project Headers
|
||||||
|
set(PROJECT_INCLUDES
|
||||||
|
"src"
|
||||||
|
#"src/misc/SQLite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Third-Party Headers (SYSTEM)
|
||||||
|
list(APPEND SYSTEM_INCLUDES
|
||||||
|
"src"
|
||||||
|
"sdi_toolBox_1.0.x/toolBox"
|
||||||
|
"${DEV_LIB}/wxWidgets-3.2.7/include/msvc"
|
||||||
|
"${DEV_LIB}/wxWidgets-3.2.7/include"
|
||||||
|
"${DEV_LIB}/boost_1_87_0"
|
||||||
|
"${DEV_LIB}/openssl-3.5.0/include"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Libraries directories
|
||||||
|
#$(OutDir);
|
||||||
|
link_directories("${DEV_LIB}/wxWidgets-3.2.7/lib/vc_x64_lib")
|
||||||
|
link_directories("${DEV_LIB}/boost_1_87_0/stage/lib")
|
||||||
|
link_directories("${DEV_LIB}/openssl-3.5.0/lib")
|
||||||
|
|
||||||
|
# Preprocessor definitions
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
list(APPEND PREPROCESSOR_DEFINITIONS
|
||||||
|
"_CRT_SECURE_NO_DEPRECATE"
|
||||||
|
"_CRT_NONSTDC_NO_DEPRECATE"
|
||||||
|
"_DEBUG"
|
||||||
|
"_UNICODE"
|
||||||
|
"_WINDOWS"
|
||||||
|
"DEBUG"
|
||||||
|
"NOMINMAX"
|
||||||
|
"UNICODE"
|
||||||
|
"WIN32"
|
||||||
|
"WIN32_LEAN_AND_MEAN"
|
||||||
|
"WXDEBUG"
|
||||||
|
)
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
list(APPEND PREPROCESSOR_DEFINITIONS
|
||||||
|
"_CRT_SECURE_NO_DEPRECATE"
|
||||||
|
"_CRT_NONSTDC_NO_DEPRECATE"
|
||||||
|
"_UNICODE"
|
||||||
|
"_WINDOWS"
|
||||||
|
"NDEBUG"
|
||||||
|
"NOMINMAX"
|
||||||
|
"UNICODE"
|
||||||
|
"WIN32"
|
||||||
|
"WIN32_LEAN_AND_MEAN"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Invalid build type")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# External libraries
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(EXTERNAL_LIB
|
||||||
|
"libssl.lib"
|
||||||
|
"libcrypto.lib"
|
||||||
|
)
|
||||||
|
list(APPEND EXTERNAL_BIN
|
||||||
|
# "${CMAKE_SOURCE_DIR}/dependencies/Debug/thromboSoft_lib.dll"
|
||||||
|
)
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
set(EXTERNAL_LIB
|
||||||
|
"libssl.lib"
|
||||||
|
"libcrypto.lib"
|
||||||
|
)
|
||||||
|
list(APPEND EXTERNAL_BIN
|
||||||
|
# "${CMAKE_SOURCE_DIR}/dependencies/Release/thromboSoft_lib.dll"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Invalid build type")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND EXTERNAL_BIN
|
||||||
|
"${DEV_LIB}/openssl-3.5.0/bin/libcrypto-3-x64.dll"
|
||||||
|
"${DEV_LIB}/openssl-3.5.0/bin/libssl-3-x64.dll"
|
||||||
|
)
|
||||||
|
|
||||||
|
# External resources
|
||||||
|
set(RESOURCE_FILES "src/kwaFr.rc")
|
||||||
|
|
||||||
|
# Activez Rechargement à chaud pour les compilateurs MSVC si cela est pris en charge.
|
||||||
|
if (POLICY CMP0141)
|
||||||
|
cmake_policy(SET CMP0141 NEW)
|
||||||
|
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#------------------------------
|
||||||
|
#--- Project files
|
||||||
|
#---------------
|
||||||
|
file(GLOB_RECURSE SOURCES_C "src/*.c")
|
||||||
|
file(GLOB_RECURSE SOURCES_CPP "src/*.cpp")
|
||||||
|
|
||||||
|
if(NOT DEFINED TESTS_MODE)
|
||||||
|
add_executable (${PROJECT_NAME} WIN32 ${SOURCES_C} ${SOURCES_CPP} ${RESOURCE_FILES})
|
||||||
|
elseif(TESTS_MODE STREQUAL "True")
|
||||||
|
add_executable (${PROJECT_NAME} ${SOURCES_C} ${SOURCES_CPP} ${RESOURCE_FILES})
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Invalid build type")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE ${PREPROCESSOR_DEFINITIONS})
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE ${EXTERNAL_LIB})
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE "/ignore:4099")
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDES})
|
||||||
|
target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${SYSTEM_INCLUDES})
|
||||||
|
|
||||||
|
# Copy required DLL
|
||||||
|
foreach(DLL ${EXTERNAL_BIN})
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${DLL}"
|
||||||
|
"${CMAKE_BINARY_DIR}"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(${PROJECT_NAME} PRIVATE /MP)
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE /PROFILE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------
|
||||||
|
#--- Compiler specific information ------------
|
||||||
|
#----------------------------------------------
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "┏(-_-)┛┗(-_-)┓┗(-_-)┛┏(-_-)┓┏(-_-)┓┗(-_-)┛┏(-_-)┛┗(-_-)┓")
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "--- Platform Information -------------------------------")
|
||||||
|
message(STATUS "System Name: ${CMAKE_SYSTEM_NAME}")
|
||||||
|
message(STATUS "Processor: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
message(STATUS "CMake Version: ${CMAKE_VERSION}")
|
||||||
|
message(STATUS "CMake Generator: ${CMAKE_GENERATOR}")
|
||||||
|
#message(STATUS "Install Prefix: ${CMAKE_INSTALL_PREFIX}")
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "--- Compiler/Language Settings -------------------------")
|
||||||
|
message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
|
||||||
|
message(STATUS "C Standard: ${CMAKE_C_STANDARD}")
|
||||||
|
message(STATUS "C Compiler ID: ${CMAKE_C_COMPILER_ID}")
|
||||||
|
message(STATUS "C Common Compiler Flags: ${CMAKE_C_FLAGS}")
|
||||||
|
message(STATUS "C Debug Compiler Flags: ${CMAKE_C_FLAGS_DEBUG}")
|
||||||
|
message(STATUS "C Release Compiler Flags: ${CMAKE_C_FLAGS_RELEASE}")
|
||||||
|
message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}")
|
||||||
|
message(STATUS "C++ Compiler ID: ${CMAKE_CXX_COMPILER_ID}")
|
||||||
|
message(STATUS "C++ Common Compiler Flags: ${CMAKE_CXX_FLAGS}")
|
||||||
|
message(STATUS "C++ Debug Compiler Flags: ${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
|
message(STATUS "C++ Release Compiler Flags: ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
message(STATUS "Common Linker Flags: ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
message(STATUS "Debug Linker Flags: ${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
|
||||||
|
message(STATUS "Release Linker Flags: ${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "--- Path Information -----------------------------------")
|
||||||
|
message(STATUS "Source Dir: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
message(STATUS "Binary Dir: ${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
if(MSVC)
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "--- Microsoft Visual C++ (MSVC) Information ------------")
|
||||||
|
# Check the major compiler version
|
||||||
|
if(MSVC_VERSION GREATER_EQUAL 1950)
|
||||||
|
message(STATUS "MSVC Compiler Version: ${MSVC_VERSION} (Visual Studio 2026 or newer)")
|
||||||
|
elseif(MSVC_VERSION GREATER_EQUAL 1930)
|
||||||
|
message(STATUS "MSVC Compiler Version: ${MSVC_VERSION} (Visual Studio 2022)")
|
||||||
|
endif()
|
||||||
|
message(STATUS "MSVC Toolset Version: ${MSVC_TOOLSET_VERSION}")
|
||||||
|
message(STATUS "CPP Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
|
message(STATUS "C Compiler Version: ${CMAKE_C_COMPILER_VERSION}")
|
||||||
|
endif()
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "--- Project Information --------------------------------")
|
||||||
|
message(STATUS "Project Name: ${PROJECT_NAME}")
|
||||||
|
message(STATUS "Preset Name: ${PRESET_NAME}")
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
message(STATUS "┏(-_-)┛┗(-_-)┓┗(-_-)┛┏(-_-)┓┏(-_-)┓┗(-_-)┛┏(-_-)┛┗(-_-)┓")
|
||||||
|
message(STATUS "--------------------------------------------------------")
|
||||||
|
|
||||||
59
CMakePresets.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "windows-base",
|
||||||
|
"hidden": true,
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||||
|
"installDir": "${sourceDir}/out/install/${presetName}",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_C_COMPILER": "cl.exe",
|
||||||
|
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||||
|
},
|
||||||
|
"condition": {
|
||||||
|
"type": "equals",
|
||||||
|
"lhs": "${hostSystemName}",
|
||||||
|
"rhs": "Windows"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-debug",
|
||||||
|
"displayName": "x64 Debug",
|
||||||
|
"inherits": "windows-base",
|
||||||
|
"architecture": {
|
||||||
|
"value": "x64",
|
||||||
|
"strategy": "external"
|
||||||
|
},
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-release",
|
||||||
|
"displayName": "x64 Release",
|
||||||
|
"inherits": "x64-debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-debug-tests",
|
||||||
|
"displayName": "x64 Debug Test",
|
||||||
|
"inherits": "x64-debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug",
|
||||||
|
"TESTS_MODE": "True"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-release-tests",
|
||||||
|
"displayName": "x64 Release Test",
|
||||||
|
"inherits": "x64-debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release",
|
||||||
|
"TESTS_MODE": "True"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
assets/bin2c.exe
Normal file
36
assets/floppy-disk-solid-full.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="floppy-disk-solid-full.svg"
|
||||||
|
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.2609375"
|
||||||
|
inkscape:cx="320"
|
||||||
|
inkscape:cy="320"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="-5"
|
||||||
|
inkscape:window-y="2"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
|
||||||
|
<path
|
||||||
|
d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"
|
||||||
|
id="path1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/icons_24x24/bin2c.exe
Normal file
BIN
assets/icons_24x24/icon_floppy-disk-solid-full_24x24.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
assets/icons_24x24/icon_rotate-left-solid-full_24x24.png
Normal file
|
After Width: | Height: | Size: 529 B |
BIN
assets/icons_24x24/icon_xmark-solid-full_24x24.png
Normal file
|
After Width: | Height: | Size: 343 B |
36
assets/minus-solid-full.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="minus-solid-full.svg"
|
||||||
|
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.2609375"
|
||||||
|
inkscape:cx="319.60347"
|
||||||
|
inkscape:cy="320"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="1915"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
|
||||||
|
<path
|
||||||
|
d="M96 320C96 302.3 110.3 288 128 288L512 288C529.7 288 544 302.3 544 320C544 337.7 529.7 352 512 352L128 352C110.3 352 96 337.7 96 320z"
|
||||||
|
id="path1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
36
assets/plus-solid-full.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="plus-solid-full.svg"
|
||||||
|
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.2609375"
|
||||||
|
inkscape:cx="319.60347"
|
||||||
|
inkscape:cy="320"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="1915"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
|
||||||
|
<path
|
||||||
|
d="M352 128C352 110.3 337.7 96 320 96C302.3 96 288 110.3 288 128L288 288L128 288C110.3 288 96 302.3 96 320C96 337.7 110.3 352 128 352L288 352L288 512C288 529.7 302.3 544 320 544C337.7 544 352 529.7 352 512L352 352L512 352C529.7 352 544 337.7 544 320C544 302.3 529.7 288 512 288L352 288L352 128z"
|
||||||
|
id="path1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
36
assets/rotate-left-solid-full.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="rotate-left-solid-full.svg"
|
||||||
|
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.2609375"
|
||||||
|
inkscape:cx="320"
|
||||||
|
inkscape:cy="320"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="-5"
|
||||||
|
inkscape:window-y="2"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
|
||||||
|
<path
|
||||||
|
d="M88 256L232 256C241.7 256 250.5 250.2 254.2 241.2C257.9 232.2 255.9 221.9 249 215L202.3 168.3C277.6 109.7 386.6 115 455.8 184.2C530.8 259.2 530.8 380.7 455.8 455.7C380.8 530.7 259.3 530.7 184.3 455.7C174.1 445.5 165.3 434.4 157.9 422.7C148.4 407.8 128.6 403.4 113.7 412.9C98.8 422.4 94.4 442.2 103.9 457.1C113.7 472.7 125.4 487.5 139 501C239 601 401 601 501 501C601 401 601 239 501 139C406.8 44.7 257.3 39.3 156.7 122.8L105 71C98.1 64.2 87.8 62.1 78.8 65.8C69.8 69.5 64 78.3 64 88L64 232C64 245.3 74.7 256 88 256z"
|
||||||
|
id="path1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
36
assets/xmark-solid-full.svg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 640 640"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="xmark-solid-full.svg"
|
||||||
|
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.2609375"
|
||||||
|
inkscape:cx="319.60347"
|
||||||
|
inkscape:cy="320"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="-5"
|
||||||
|
inkscape:window-y="2"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
|
||||||
|
<path
|
||||||
|
d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"
|
||||||
|
id="path1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
430
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/curlHttpClient.h
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sdi_toolBox/logs/reLog.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace sdi_ToolBox::communication
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class CurlGlobalInitializer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void init() // Initialize libcurl globally
|
||||||
|
{
|
||||||
|
static auto instance = std::unique_ptr<CurlGlobalInitializer>(new CurlGlobalInitializer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~CurlGlobalInitializer() // Default destructor
|
||||||
|
{
|
||||||
|
curl_global_cleanup();
|
||||||
|
}
|
||||||
|
CurlGlobalInitializer(const CurlGlobalInitializer &obj) = delete; // Copy constructor
|
||||||
|
CurlGlobalInitializer(CurlGlobalInitializer &&obj) noexcept = delete; // Move constructor
|
||||||
|
CurlGlobalInitializer &operator=(const CurlGlobalInitializer &obj) = delete; // Copy assignment operator
|
||||||
|
CurlGlobalInitializer &operator=(CurlGlobalInitializer &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CurlGlobalInitializer() // Default constructor
|
||||||
|
{
|
||||||
|
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
|
||||||
|
throw std::runtime_error("ERROR: Failed to initialize cURL");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class CurlHttpClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Method : uint8_t
|
||||||
|
{
|
||||||
|
http_GET,
|
||||||
|
http_POST,
|
||||||
|
http_PUT,
|
||||||
|
http_DELETE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Settings
|
||||||
|
{
|
||||||
|
bool enableSSL = true;
|
||||||
|
bool ssl_verifyPeer = true;
|
||||||
|
bool ssl_verifyHost = true;
|
||||||
|
std::filesystem::path ssl_caInfo;
|
||||||
|
std::string ssl_certBlob;
|
||||||
|
std::chrono::seconds timeout{ 10 };
|
||||||
|
|
||||||
|
bool operator==(const Settings &other) const
|
||||||
|
{
|
||||||
|
// Compare all fields against the other object
|
||||||
|
return (enableSSL == other.enableSSL &&
|
||||||
|
ssl_verifyPeer == other.ssl_verifyPeer &&
|
||||||
|
ssl_verifyHost == other.ssl_verifyHost &&
|
||||||
|
ssl_caInfo == other.ssl_caInfo &&
|
||||||
|
ssl_certBlob == other.ssl_certBlob);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using Headers = std::unordered_map<std::string, std::string>;
|
||||||
|
|
||||||
|
struct Request
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
Method method = Method::http_GET;
|
||||||
|
std::string body;
|
||||||
|
Headers headers;
|
||||||
|
Settings settings;
|
||||||
|
|
||||||
|
bool operator==(const Request &other) const
|
||||||
|
{
|
||||||
|
// Compare all fields against the other object
|
||||||
|
return (url == other.url &&
|
||||||
|
method == other.method &&
|
||||||
|
body == other.body &&
|
||||||
|
headers == other.headers &&
|
||||||
|
settings == other.settings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct Response
|
||||||
|
{
|
||||||
|
int returnCode = -1;
|
||||||
|
std::string content;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
CurlHttpClient(); // Default constructor
|
||||||
|
virtual ~CurlHttpClient() = default; // Default destructor
|
||||||
|
CurlHttpClient(const CurlHttpClient &obj) = delete; // Copy constructor
|
||||||
|
CurlHttpClient(CurlHttpClient &&obj) noexcept = delete; // Move constructor
|
||||||
|
CurlHttpClient &operator=(const CurlHttpClient &obj) = delete; // Copy assignment operator
|
||||||
|
CurlHttpClient &operator=(CurlHttpClient &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
const Response &performRequest(const Request &request = {}); // Perform HTTP request
|
||||||
|
const Response &performRequest_nothrow(const Request &request = {}); // Perform HTTP request
|
||||||
|
|
||||||
|
const Request &getRequest() const; // Retrieves curl request
|
||||||
|
const Response &getResponse() const; // Retrieves curl response
|
||||||
|
|
||||||
|
protected:
|
||||||
|
mutable std::mutex m_mtx;
|
||||||
|
|
||||||
|
CURL *m_curl = nullptr;
|
||||||
|
curl_slist *m_headerList = nullptr;
|
||||||
|
Request m_request;
|
||||||
|
Response m_response;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CURLcode setRequestOptions(); // Set cURL request options
|
||||||
|
|
||||||
|
static size_t write_callback(void *contents, // Callback for writing received data
|
||||||
|
size_t size,
|
||||||
|
size_t itemCount,
|
||||||
|
void *userData);
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
inline CurlHttpClient::CurlHttpClient()
|
||||||
|
{
|
||||||
|
CurlGlobalInitializer::init();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Perform HTTP request */
|
||||||
|
inline const CurlHttpClient::Response &CurlHttpClient::performRequest(const Request &request)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
if (const Request default_req = {}; request != default_req)
|
||||||
|
m_request = request;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_curl = curl_easy_init();
|
||||||
|
if (!m_curl)
|
||||||
|
throw std::runtime_error("unable to initialize curl library");
|
||||||
|
|
||||||
|
// Set request options
|
||||||
|
if (const auto res = setRequestOptions(); res != CURLE_OK)
|
||||||
|
throw std::runtime_error(std::format("ERROR: Failed to set up CURL options: {}", curl_easy_strerror(res)));
|
||||||
|
|
||||||
|
// Perform request
|
||||||
|
if (const auto res = curl_easy_perform(m_curl); res != CURLE_OK)
|
||||||
|
throw std::runtime_error(std::format("ERROR: curl_easy_perform() failed: {}", curl_easy_strerror(res)));
|
||||||
|
if (const auto res = curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &m_response.returnCode); res != CURLE_OK)
|
||||||
|
throw std::runtime_error(std::format("ERROR: Failed to get content code: {}", curl_easy_strerror(res)));
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
m_response.returnCode = -1;
|
||||||
|
m_response.content = e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free resources
|
||||||
|
if (m_curl)
|
||||||
|
{
|
||||||
|
curl_easy_cleanup(m_curl); // free the easy curl handle
|
||||||
|
m_curl = nullptr;
|
||||||
|
}
|
||||||
|
if (m_headerList)
|
||||||
|
{
|
||||||
|
curl_slist_free_all(m_headerList); // free the custom headers
|
||||||
|
m_curl = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_response.returnCode == -1)
|
||||||
|
throw std::runtime_error(m_response.content);
|
||||||
|
|
||||||
|
return m_response;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Perform HTTP request */
|
||||||
|
inline const CurlHttpClient::Response &CurlHttpClient::performRequest_nothrow(const Request &request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
performRequest(request);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
LogError() << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_response;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Retrieves curl request */
|
||||||
|
inline const CurlHttpClient::Request &CurlHttpClient::getRequest() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
return m_request;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Retrieves curl response */
|
||||||
|
inline const CurlHttpClient::Response &CurlHttpClient::getResponse() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
return m_response;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set cURL request options */
|
||||||
|
inline CURLcode CurlHttpClient::setRequestOptions()
|
||||||
|
{
|
||||||
|
// --- Common Options ---
|
||||||
|
// Set the target URL
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_URL, m_request.url.c_str()); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Set the write callback function
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_callback); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Set the user data pointer for the callback (our content buffer)
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &m_response.content); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Set custom headers if any
|
||||||
|
if (m_headerList)
|
||||||
|
{
|
||||||
|
// Prepare header list
|
||||||
|
for (const auto &[key, value] : m_request.headers)
|
||||||
|
{
|
||||||
|
const auto header = format("{0:}: {1:}", key, value);
|
||||||
|
m_headerList = curl_slist_append(m_headerList, header.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append headers to the request
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headerList); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the max time in seconds for the entire operation (including connection and transfer)
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, m_request.settings.timeout.count()); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// --- SSL/TLS Options ---
|
||||||
|
// curl documentation recommends setting both for full control
|
||||||
|
if (m_request.settings.enableSSL)
|
||||||
|
{
|
||||||
|
// Enable peer certificate verification
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Enable host name verification (prevents 'Man-in-the-middle' attacks)
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (!m_request.settings.ssl_caInfo.empty())
|
||||||
|
{
|
||||||
|
// Optionally, set the path to a CA bundle file (often not needed
|
||||||
|
// if curl is built with a system-wide CA store)
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_request.settings.ssl_caInfo.c_str()); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else if (!m_request.settings.ssl_certBlob.empty())
|
||||||
|
{
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSLKEY_BLOB, &m_request.settings.ssl_certBlob); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Disable verification (USE WITH CAUTION: Insecure!)
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Method-Specific Options ---
|
||||||
|
switch (m_request.method)
|
||||||
|
{
|
||||||
|
using enum Method;
|
||||||
|
case http_GET:
|
||||||
|
{
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case http_POST:
|
||||||
|
{
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POST, 1L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Set the data to be sent
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case http_PUT:
|
||||||
|
{
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_PUT, 1L); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "PUT"); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case http_DELETE:
|
||||||
|
{
|
||||||
|
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); res != CURLE_OK)
|
||||||
|
return res;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Callback for writing received data */
|
||||||
|
inline size_t CurlHttpClient::write_callback(void *contents, const size_t size, const size_t itemCount, void *userData)
|
||||||
|
{
|
||||||
|
// Total size of the incoming buffer
|
||||||
|
const auto realSize = size * itemCount;
|
||||||
|
|
||||||
|
// Append the received data to the content string
|
||||||
|
const auto &response = static_cast<std::string *>(userData);
|
||||||
|
response->append(static_cast<char *>(contents), realSize);
|
||||||
|
|
||||||
|
return realSize;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class CurlInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CurlInfo(); // Default constructor
|
||||||
|
virtual ~CurlInfo() = default; // Default destructor
|
||||||
|
CurlInfo(const CurlInfo &obj) = delete; // Copy constructor
|
||||||
|
CurlInfo(CurlInfo &&obj) noexcept = delete; // Move constructor
|
||||||
|
CurlInfo &operator=(const CurlInfo &obj) = delete; // Copy assignment operator
|
||||||
|
CurlInfo &operator=(CurlInfo &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
void toStream(std::ostream &stream); // Print cURL information to stream object
|
||||||
|
void print(); // Print cURL information to cout
|
||||||
|
|
||||||
|
std::string version;
|
||||||
|
uint32_t features;
|
||||||
|
|
||||||
|
bool ssl_support = false;
|
||||||
|
std::string ssl_version;
|
||||||
|
|
||||||
|
std::vector<std::string> availableProtocols;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
inline CurlInfo::CurlInfo()
|
||||||
|
{
|
||||||
|
CurlGlobalInitializer::init();
|
||||||
|
|
||||||
|
// Retrieves information from the library
|
||||||
|
const auto info = curl_version_info(CURLVERSION_NOW);
|
||||||
|
|
||||||
|
version = info->version;
|
||||||
|
features = info->features;
|
||||||
|
|
||||||
|
ssl_support = info->features & CURL_VERSION_SSL;
|
||||||
|
ssl_version = info->ssl_version;
|
||||||
|
|
||||||
|
if (info->protocols)
|
||||||
|
{
|
||||||
|
for (const char *const *proto = info->protocols; *proto; ++proto)
|
||||||
|
availableProtocols.emplace_back(*proto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Print cURL information to stream object */
|
||||||
|
inline void CurlInfo::toStream(std::ostream &stream)
|
||||||
|
{
|
||||||
|
stream << "--- libcurl Version Info ---" << "\n";
|
||||||
|
stream << "Version : " << version << "\n";
|
||||||
|
stream << "Features (bitmask) : " << std::format("0x{0:X}", features) << "\n";
|
||||||
|
stream << "SSL/TLS support : " << ((ssl_support) ? std::format("**Yes** [{0:}]", ssl_version) : "No") << "\n";
|
||||||
|
if (!availableProtocols.empty())
|
||||||
|
{
|
||||||
|
stream << "Available protocols: ";
|
||||||
|
bool first = true;
|
||||||
|
for (const auto protocol : availableProtocols)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
stream << ", ";
|
||||||
|
stream << protocol;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
stream << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Print cURL information to cout */
|
||||||
|
inline void CurlInfo::print()
|
||||||
|
{
|
||||||
|
toStream(std::cout);
|
||||||
|
std::cout << std::flush;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_ToolBox::communication
|
||||||
6
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/namespace.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace sdi_ToolBox::comm
|
||||||
|
* @brief Communication protocols and binary handling.
|
||||||
|
*/
|
||||||
784
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/uBinaryFrame.h
Normal file
@@ -0,0 +1,784 @@
|
|||||||
|
//{{copyright}}
|
||||||
|
|
||||||
|
//{{version}}
|
||||||
|
|
||||||
|
//{{license}}
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../generic/crc.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace sdi_ToolBox::comm::uBinaryFrame
|
||||||
|
* @brief Utilities for robust binary frame serialization, deserialization, and protocol handling.
|
||||||
|
*
|
||||||
|
* This namespace provides classes and constants to manage binary communication frames with automatic
|
||||||
|
* escaping, CRC calculation, and protocol validation. The supported frame format is:
|
||||||
|
* [SOF1][SOF2][SIZE][PAYLOAD][CRC16][EOF]
|
||||||
|
* where special bytes are escaped as needed.
|
||||||
|
*
|
||||||
|
* Main components:
|
||||||
|
* - @ref FrameReceiver : Receives and extracts a complete binary frame from a byte stream.
|
||||||
|
* - @ref MessageParser : Parses and validates a binary frame, providing typed access to the payload.
|
||||||
|
* - @ref MessageBuilder : Builds a binary message with CRC calculation and escaping.
|
||||||
|
* - @ref FramePacker : Packs a message into a complete frame ready for transmission (header, CRC, escaping, etc.).
|
||||||
|
*/
|
||||||
|
namespace sdi_ToolBox::comm::uBinaryFrame
|
||||||
|
{
|
||||||
|
constexpr uint8_t SOF1_ = 0xAA; // Start of Frame byte (0b10101010 - useful for bus synchronisation)
|
||||||
|
constexpr uint8_t SOF2_ = 0x55; // Start of Frame byte (0b01010101 - useful for bus synchronisation)
|
||||||
|
constexpr uint8_t EOF_ = 0x03; // End of Frame byte (End of Text code in ASCII table)
|
||||||
|
constexpr uint8_t ESC_ = 0x1B; // Escape byte (Escape code in ASCII table)
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class FrameReceiver
|
||||||
|
* @brief Stateful receiver for extracting a complete binary frame from a byte stream.
|
||||||
|
*
|
||||||
|
* Handles frame synchronization, byte un-escaping, and buffering.
|
||||||
|
* The frame is considered complete when the protocol's end-of-frame byte is detected.
|
||||||
|
*
|
||||||
|
* @tparam BUFFER_SIZE Maximum size of the internal receive buffer.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Call push(byte) for each received byte.
|
||||||
|
* - When push() returns true or isFrameAvailable() is true, use getFrame() to access the frame data.
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* FrameReceiver<128> receiver;
|
||||||
|
* for (uint8_t byte : incomingBytes) {
|
||||||
|
* if (receiver.push(byte)) {
|
||||||
|
* auto frame = receiver.getFrame();
|
||||||
|
* // Process frame...
|
||||||
|
*
|
||||||
|
* receiver.reset();
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
class FrameReceiver
|
||||||
|
{
|
||||||
|
enum class State : uint8_t
|
||||||
|
{
|
||||||
|
WAIT_SOF1,
|
||||||
|
WAIT_SOF2,
|
||||||
|
RECEIVE_DATA,
|
||||||
|
WAIT_EOF,
|
||||||
|
ESCAPE_NEXT,
|
||||||
|
END,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
FrameReceiver() = default; // Default constructor
|
||||||
|
~FrameReceiver() = default; // Default destructor
|
||||||
|
FrameReceiver(const FrameReceiver &obj) = default; // Copy constructor
|
||||||
|
FrameReceiver(FrameReceiver &&obj) noexcept = default; // Move constructor
|
||||||
|
FrameReceiver &operator=(const FrameReceiver &obj) = default; // Copy assignment operator
|
||||||
|
FrameReceiver &operator=(FrameReceiver &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the receiver state and clears the internal buffer.
|
||||||
|
*
|
||||||
|
* After calling this method, the receiver is ready to process a new frame.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pushes a byte into the receiver buffer and updates the state machine.
|
||||||
|
* @param byte The received byte to process.
|
||||||
|
* @return true if a complete frame has been received and is available, false otherwise.
|
||||||
|
*
|
||||||
|
* @note If a frame is available, use getFrame() to access its content.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool push(uint8_t byte);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a complete frame is available in the buffer.
|
||||||
|
* @return true if a frame is available, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isFrameAvailable() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a span to the complete frame data.
|
||||||
|
* @return A span containing the frame bytes, or an empty span if no frame is available.
|
||||||
|
*
|
||||||
|
* @note The returned span is only valid until the next reset() or push().
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::span<const uint8_t> getFrame() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
|
||||||
|
size_t m_index = 0; // Current index in the buffer
|
||||||
|
State m_state = State::WAIT_SOF1; // Current state of the receiver
|
||||||
|
|
||||||
|
private:
|
||||||
|
void appendByte(uint8_t byte); // Append a byte to the buffer
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline void FrameReceiver<BUFFER_SIZE>::reset()
|
||||||
|
{
|
||||||
|
m_index = 0;
|
||||||
|
m_state = State::WAIT_SOF1;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline bool FrameReceiver<BUFFER_SIZE>::push(const uint8_t byte)
|
||||||
|
{
|
||||||
|
switch (m_state)
|
||||||
|
{
|
||||||
|
// Initial state: waiting for SOF1
|
||||||
|
case State::WAIT_SOF1:
|
||||||
|
{
|
||||||
|
if (byte == SOF1_)
|
||||||
|
{
|
||||||
|
m_index = 0;
|
||||||
|
m_state = State::WAIT_SOF2; // Next byte should be SOF2
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waiting for SOF2
|
||||||
|
case State::WAIT_SOF2:
|
||||||
|
{
|
||||||
|
if (byte == SOF2_)
|
||||||
|
m_state = State::RECEIVE_DATA; // Start receiving data
|
||||||
|
else
|
||||||
|
m_state = State::WAIT_SOF1; // Abort frame reception
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receiving data
|
||||||
|
case State::RECEIVE_DATA:
|
||||||
|
{
|
||||||
|
if (byte == ESC_)
|
||||||
|
m_state = State::ESCAPE_NEXT; // Next byte is escaped
|
||||||
|
else if (byte == SOF1_)
|
||||||
|
m_state = State::WAIT_SOF2; // Restart frame reception
|
||||||
|
else if (byte == SOF2_)
|
||||||
|
m_state = State::WAIT_SOF1; // Abort frame reception
|
||||||
|
else if (byte == EOF_)
|
||||||
|
{
|
||||||
|
m_state = State::END; // Frame complete
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
appendByte(byte);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling escaped byte
|
||||||
|
case State::ESCAPE_NEXT:
|
||||||
|
{
|
||||||
|
appendByte(byte);
|
||||||
|
m_state = State::RECEIVE_DATA; // Return to data reception
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame complete
|
||||||
|
case State::END:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline bool FrameReceiver<BUFFER_SIZE>::isFrameAvailable() const
|
||||||
|
{
|
||||||
|
return m_state == State::END;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline std::span<const uint8_t> FrameReceiver<BUFFER_SIZE>::getFrame() const
|
||||||
|
{
|
||||||
|
if (m_state == State::END)
|
||||||
|
return { m_buffer.data(), m_index };
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline void FrameReceiver<BUFFER_SIZE>::appendByte(const uint8_t byte)
|
||||||
|
{
|
||||||
|
if (m_index < BUFFER_SIZE)
|
||||||
|
m_buffer[m_index++] = byte;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class MessageParser
|
||||||
|
* @brief Parses and validates a binary frame, providing typed access to the payload.
|
||||||
|
*
|
||||||
|
* Checks frame structure, size, CRC, and end-of-frame marker.
|
||||||
|
* Allows extraction of typed values or raw data from the payload.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Construct with a frame buffer.
|
||||||
|
* - Check validity with isValid() or getStatus().
|
||||||
|
* - Use get<T>(offset, value) to extract typed data from the payload.
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* MessageParser parser(frame);
|
||||||
|
* if (parser.isValid()) {
|
||||||
|
* uint16_t id;
|
||||||
|
* parser.get(0, id);
|
||||||
|
* std::array<uint8_t, 8> data;
|
||||||
|
* parser.get(2, std::span<uint8_t>(data));
|
||||||
|
* // Use id and data...
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
class MessageParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class MessageStatus : uint8_t
|
||||||
|
{
|
||||||
|
VALID,
|
||||||
|
INVALID,
|
||||||
|
BAD_SIZE,
|
||||||
|
BAD_CRC,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
MessageParser() = delete; // Default constructor
|
||||||
|
~MessageParser() = default; // Default destructor
|
||||||
|
MessageParser(const MessageParser &obj) = default; // Copy constructor
|
||||||
|
MessageParser(MessageParser &&obj) noexcept = default; // Move constructor
|
||||||
|
MessageParser &operator=(const MessageParser &obj) = default; // Copy assignment operator
|
||||||
|
MessageParser &operator=(MessageParser &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a MessageParser and checks the integrity of the provided frame.
|
||||||
|
* @param buffer The buffer containing the frame to parse.
|
||||||
|
*/
|
||||||
|
explicit MessageParser(std::span<const uint8_t> buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the parsed message is valid.
|
||||||
|
* @return true if the message is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isValid() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current message validity status.
|
||||||
|
* @return The status as a MessageStatus enum value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] MessageStatus getStatus() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the message payload buffer.
|
||||||
|
* @return A span containing the payload bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::span<const uint8_t> getBuffer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the size of the message payload.
|
||||||
|
* @return The size of the payload in bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t size() const; // Get message payload size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extracts a value of type T from the message payload at the specified offset.
|
||||||
|
* @tparam T The type of the value to extract (POD type or std::span<uint8_t>).
|
||||||
|
* @param offset The offset in the payload to read from.
|
||||||
|
* @param value Reference to store the extracted value.
|
||||||
|
* @return The new offset after reading the value, or the original offset if reading failed.
|
||||||
|
*
|
||||||
|
* @note No exception is thrown. If extraction fails, value is not modified.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
[[nodiscard]] size_t get(size_t offset, T &value) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::span<const uint8_t> m_buffer; // Message buffer
|
||||||
|
MessageStatus m_messageStatus = MessageStatus::INVALID; // Message validity status
|
||||||
|
|
||||||
|
private:
|
||||||
|
void messageCheck(); // Check message integrity
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline MessageParser::MessageParser(const std::span<const uint8_t> buffer)
|
||||||
|
{
|
||||||
|
m_buffer = buffer;
|
||||||
|
messageCheck();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline bool MessageParser::isValid() const
|
||||||
|
{
|
||||||
|
return m_messageStatus == MessageStatus::VALID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get message validity status */
|
||||||
|
inline MessageParser::MessageStatus MessageParser::getStatus() const
|
||||||
|
{
|
||||||
|
return m_messageStatus;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get message payload buffer */
|
||||||
|
inline std::span<const uint8_t> MessageParser::getBuffer() const
|
||||||
|
{
|
||||||
|
return m_buffer;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get message payload size */
|
||||||
|
inline size_t MessageParser::size() const
|
||||||
|
{
|
||||||
|
return m_buffer.size();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class T>
|
||||||
|
inline size_t MessageParser::get(size_t offset, T &value) const
|
||||||
|
{
|
||||||
|
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
|
||||||
|
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
|
||||||
|
std::is_same_v<T, std::string>); // std::string type
|
||||||
|
|
||||||
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
||||||
|
{
|
||||||
|
// POD type
|
||||||
|
constexpr size_t typeSize = sizeof(T);
|
||||||
|
if (offset + typeSize <= m_buffer.size())
|
||||||
|
{
|
||||||
|
std::memcpy(&value, m_buffer.data() + offset, typeSize);
|
||||||
|
return offset + typeSize; // Return new offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::span<uint8_t>>)
|
||||||
|
{
|
||||||
|
// std::span<uint8_t> type
|
||||||
|
if (offset + value.size() <= m_buffer.size())
|
||||||
|
{
|
||||||
|
std::memcpy(value.data(), m_buffer.data() + offset, value.size());
|
||||||
|
return offset + value.size(); // Return new offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>)
|
||||||
|
{
|
||||||
|
// std::string type
|
||||||
|
uint8_t strSize;
|
||||||
|
offset = get(offset, strSize);
|
||||||
|
|
||||||
|
value.resize(strSize);
|
||||||
|
std::span<uint8_t> strSpan(reinterpret_cast<uint8_t *>(value.data()), strSize);
|
||||||
|
offset = get(offset, strSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset; // Unable to read value, return original offset
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check message integrity */
|
||||||
|
inline void MessageParser::messageCheck()
|
||||||
|
{
|
||||||
|
/* Message format : [SOF1(1)][SOF2(1)][SIZE(1)][PAYLOAD(n)][CRC16(2)][EOF(1)] */
|
||||||
|
// Check SIZE
|
||||||
|
const auto size = m_buffer[0];
|
||||||
|
if (constexpr size_t CRCSize = 2; m_buffer.size() != CRCSize + size + sizeof(size))
|
||||||
|
{
|
||||||
|
m_messageStatus = MessageStatus::BAD_SIZE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check CRC16
|
||||||
|
const auto loCRC = *(m_buffer.rbegin() + 1);
|
||||||
|
const auto hiCRC = *m_buffer.rbegin();
|
||||||
|
const uint16_t frameCRC = static_cast<uint16_t>(hiCRC << 8) | static_cast<uint16_t>(loCRC);
|
||||||
|
const uint16_t computedCRC = generic::CRC16_modbus::computeCRC(m_buffer.subspan(1, m_buffer.size() - 3));
|
||||||
|
if (frameCRC != computedCRC)
|
||||||
|
{
|
||||||
|
m_messageStatus = MessageStatus::BAD_CRC;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buffer = m_buffer.subspan(1, size);
|
||||||
|
m_messageStatus = MessageStatus::VALID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class MessageBuilderHelper
|
||||||
|
* @brief Utility class to simplify the usage of MessageBuilder templates.
|
||||||
|
*
|
||||||
|
* This helper provides convenient methods and type aliases to work with MessageBuilder
|
||||||
|
* instances without exposing template parameters in function signatures.
|
||||||
|
*/
|
||||||
|
class MessageBuilderHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageBuilderHelper() = default; // Default constructor
|
||||||
|
virtual ~MessageBuilderHelper() = default; // Default destructor
|
||||||
|
MessageBuilderHelper(const MessageBuilderHelper &obj) = default; // Copy constructor
|
||||||
|
MessageBuilderHelper(MessageBuilderHelper &&obj) noexcept = default; // Move constructor
|
||||||
|
MessageBuilderHelper &operator=(const MessageBuilderHelper &obj) = default; // Copy assignment operator
|
||||||
|
MessageBuilderHelper &operator=(MessageBuilderHelper &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current message buffer as a span.
|
||||||
|
* @return A span containing the current message bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::span<const uint8_t> getBuffer() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the message buffer is full.
|
||||||
|
* @return true if the buffer is full, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual bool isFull() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current size of the message buffer.
|
||||||
|
* @return The number of bytes currently in the message buffer.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual size_t getBufferSize() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current CRC16 value of the message.
|
||||||
|
* @return The current CRC16 value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual uint16_t getCRC() const = 0;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class MessageBuilder
|
||||||
|
* @brief Builds a binary message payload with CRC calculation and escaping support.
|
||||||
|
*
|
||||||
|
* Supports appending POD types or byte spans, and computes CRC incrementally.
|
||||||
|
* Provides methods to reset, append, and retrieve the current buffer and CRC.
|
||||||
|
*
|
||||||
|
* @tparam BUFFER_SIZE Maximum size of the internal message buffer.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Use append() or operator<< to add data.
|
||||||
|
* - Retrieve the buffer with getBuffer() and CRC with getCRC().
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* MessageBuilder<64> builder;
|
||||||
|
* builder << uint16_t(0x1234) << std::span<const uint8_t>(data, dataLen);
|
||||||
|
* auto buffer = builder.getBuffer();
|
||||||
|
* auto crc = builder.getCRC();
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
class MessageBuilder : public MessageBuilderHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageBuilder(); // Default constructor
|
||||||
|
virtual ~MessageBuilder() = default; // Default destructor
|
||||||
|
MessageBuilder(const MessageBuilder &obj) = default; // Copy constructor
|
||||||
|
MessageBuilder(MessageBuilder &&obj) noexcept = default; // Move constructor
|
||||||
|
MessageBuilder &operator=(const MessageBuilder &obj) = default; // Copy assignment operator
|
||||||
|
MessageBuilder &operator=(MessageBuilder &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the message builder to an empty state.
|
||||||
|
* @return Reference to the MessageBuilder for chaining.
|
||||||
|
*/
|
||||||
|
MessageBuilder &reset(); // Reset the message builder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a value of type T to the message buffer.
|
||||||
|
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
|
||||||
|
* @param value The value to append.
|
||||||
|
* @return Reference to the MessageBuilder for chaining.
|
||||||
|
*
|
||||||
|
* @note If appending the value would exceed the buffer size, it is ignored.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
MessageBuilder &append(const T &value); // Append value of type T to the message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Overloads the << operator to append a value of type T to the message buffer.
|
||||||
|
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
|
||||||
|
* @param value The value to append.
|
||||||
|
* @return Reference to the MessageBuilder for chaining.
|
||||||
|
*
|
||||||
|
* @note If appending the value would exceed the buffer size, it is ignored.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
MessageBuilder &operator<<(const T &value);
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const uint8_t> getBuffer() const override;
|
||||||
|
[[nodiscard]] bool isFull() const override;
|
||||||
|
[[nodiscard]] size_t getBufferSize() const override;
|
||||||
|
[[nodiscard]] uint16_t getCRC() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
|
||||||
|
size_t m_index = 0; // Current index in the buffer
|
||||||
|
|
||||||
|
generic::CRC16_modbus m_CRC; // Current CRC value
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline MessageBuilder<BUFFER_SIZE>::MessageBuilder()
|
||||||
|
{
|
||||||
|
m_CRC.init();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::reset()
|
||||||
|
{
|
||||||
|
m_index = 0;
|
||||||
|
m_CRC.init();
|
||||||
|
return *this; // Return reference to self for chaining
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
template<class T>
|
||||||
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::append(const T &value)
|
||||||
|
{
|
||||||
|
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
|
||||||
|
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
|
||||||
|
std::is_same_v<T, std::span<const uint8_t>> || // std::span<const uint8_t> type
|
||||||
|
std::is_same_v<T, std::string> || // std::string type
|
||||||
|
std::is_same_v<T, const std::string>); // const std::string type
|
||||||
|
|
||||||
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
||||||
|
{
|
||||||
|
// POD type
|
||||||
|
constexpr size_t typeSize = sizeof(T);
|
||||||
|
if (m_index + typeSize <= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
std::array<uint8_t, typeSize> dataBuffer;
|
||||||
|
std::memcpy(dataBuffer.data(), &value, typeSize);
|
||||||
|
|
||||||
|
std::memcpy(m_buffer.data() + m_index, dataBuffer.data(), dataBuffer.size());
|
||||||
|
m_CRC.update(dataBuffer);
|
||||||
|
m_index += typeSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::span<uint8_t>> ||
|
||||||
|
std::is_same_v<T, std::span<const uint8_t>>)
|
||||||
|
{
|
||||||
|
// std::span<uint8_t> type
|
||||||
|
if (m_index + value.size() <= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
|
||||||
|
m_CRC.update(value);
|
||||||
|
m_index += value.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const std::string>)
|
||||||
|
{
|
||||||
|
// std::string type
|
||||||
|
if (m_index + (value.size() + sizeof(uint8_t)) <= BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
append(static_cast<uint8_t>(value.size()));
|
||||||
|
|
||||||
|
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
|
||||||
|
m_CRC.update(std::span(reinterpret_cast<const uint8_t *>(value.data()), value.size()));
|
||||||
|
m_index += value.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this; // Return reference to self for chaining
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
template<class T>
|
||||||
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::operator<<(const T &value)
|
||||||
|
{
|
||||||
|
append(value);
|
||||||
|
return *this; // Return reference to self for chaining
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline std::span<const uint8_t> MessageBuilder<BUFFER_SIZE>::getBuffer() const
|
||||||
|
{
|
||||||
|
return { m_buffer.data(), m_index };
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline bool MessageBuilder<BUFFER_SIZE>::isFull() const
|
||||||
|
{
|
||||||
|
return m_index >= BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline size_t MessageBuilder<BUFFER_SIZE>::getBufferSize() const
|
||||||
|
{
|
||||||
|
return m_index;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline uint16_t MessageBuilder<BUFFER_SIZE>::getCRC() const
|
||||||
|
{
|
||||||
|
return m_CRC.getCRC();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class FramePacker
|
||||||
|
* @brief Packs a message payload into a complete binary frame ready for transmission.
|
||||||
|
*
|
||||||
|
* Adds protocol headers, escapes special bytes, appends CRC and end-of-frame marker.
|
||||||
|
* Ensures the packed frame fits in the provided buffer size.
|
||||||
|
*
|
||||||
|
* @tparam BUFFER_SIZE Maximum size of the output frame buffer.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Call packMessage(payload, crc) to build the frame.
|
||||||
|
* - Use isValid() to check for overflow.
|
||||||
|
* - Retrieve the packed frame with getPackedFrame().
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* FramePacker<256> packer;
|
||||||
|
* if (packer.packMessage(payload, crc)) {
|
||||||
|
* auto frame = packer.getPackedFrame();
|
||||||
|
* // Send frame...
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
class FramePacker
|
||||||
|
{
|
||||||
|
static constexpr size_t CRC_SIZE = 4; // CRC16(2 x2) --> (CRC is 4 bytes to support escaped CRC)
|
||||||
|
|
||||||
|
public:
|
||||||
|
FramePacker() = default; // Default constructor
|
||||||
|
explicit FramePacker(std::span<const uint8_t> message, uint16_t crc); // Constructor
|
||||||
|
~FramePacker() = default; // Default destructor
|
||||||
|
FramePacker(const FramePacker &obj) = default; // Copy constructor
|
||||||
|
FramePacker(FramePacker &&obj) noexcept = default; // Move constructor
|
||||||
|
FramePacker &operator=(const FramePacker &obj) = default; // Copy assignment operator
|
||||||
|
FramePacker &operator=(FramePacker &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Packs the input message into a complete frame with headers, CRC, and escaping.
|
||||||
|
* @param message The message payload to pack.
|
||||||
|
* @param crc The CRC16 value to append to the frame.
|
||||||
|
* @return true if packing was successful without overflow, false otherwise.
|
||||||
|
*/
|
||||||
|
bool packMessage(std::span<const uint8_t> message, uint16_t crc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the packed frame is valid (no overflow occurred).
|
||||||
|
* @return true if the packed frame is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isValid() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the packed frame buffer as a span.
|
||||||
|
* @return A span containing the packed frame bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::span<const uint8_t> getPackedFrame() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<uint8_t, BUFFER_SIZE> m_frame{}; // Output buffer
|
||||||
|
size_t m_index = 0; // Current index in output buffer
|
||||||
|
|
||||||
|
std::array<uint8_t, CRC_SIZE> m_CRCBuffer{}; // CRC buffer with escaping
|
||||||
|
size_t m_CRCSize = 0; // Current size of CRC buffer with escaping
|
||||||
|
|
||||||
|
bool m_overflow = false; // Flag indicating if overflow occurred during packing
|
||||||
|
|
||||||
|
private:
|
||||||
|
void appendByte(uint8_t byte); // Append byte with escaping to output buffer
|
||||||
|
void appendRawByte(uint8_t byte); // Append raw byte to output buffer
|
||||||
|
|
||||||
|
void packCRC(uint16_t crc); // Pack CRC into CRC buffer with escaping
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline FramePacker<BUFFER_SIZE>::FramePacker(const std::span<const uint8_t> message, const uint16_t crc)
|
||||||
|
{
|
||||||
|
packMessage(message, crc);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline bool FramePacker<BUFFER_SIZE>::packMessage(const std::span<const uint8_t> message, const uint16_t crc)
|
||||||
|
{
|
||||||
|
// Reset state
|
||||||
|
m_index = 0;
|
||||||
|
m_overflow = false;
|
||||||
|
|
||||||
|
// Prepare CRC buffer with escaping
|
||||||
|
packCRC(crc);
|
||||||
|
|
||||||
|
// Append header
|
||||||
|
appendRawByte(SOF1_); // Append SOF1
|
||||||
|
appendRawByte(SOF2_); // Append SOF2
|
||||||
|
appendByte(static_cast<uint8_t>(message.size())); // Append payload size
|
||||||
|
|
||||||
|
// Append payload
|
||||||
|
for (const auto &byte : message)
|
||||||
|
appendByte(byte);
|
||||||
|
|
||||||
|
// Append CRC
|
||||||
|
for (size_t i = 0; i < m_CRCSize; i++)
|
||||||
|
appendRawByte(m_CRCBuffer[i]);
|
||||||
|
|
||||||
|
// Append EOF
|
||||||
|
appendRawByte(EOF_);
|
||||||
|
|
||||||
|
return !m_overflow; // Return true if packing was successful
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline bool FramePacker<BUFFER_SIZE>::isValid() const
|
||||||
|
{
|
||||||
|
return !m_overflow;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline std::span<const uint8_t> FramePacker<BUFFER_SIZE>::getPackedFrame() const
|
||||||
|
{
|
||||||
|
return std::span<const uint8_t>(m_frame.data(), m_index);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline void FramePacker<BUFFER_SIZE>::appendByte(const uint8_t byte)
|
||||||
|
{
|
||||||
|
if (byte == SOF1_ || byte == SOF2_ || byte == EOF_ || byte == ESC_)
|
||||||
|
{
|
||||||
|
appendRawByte(ESC_); // Append escape byte
|
||||||
|
}
|
||||||
|
appendRawByte(byte); // Append actual byte
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline void FramePacker<BUFFER_SIZE>::appendRawByte(const uint8_t byte)
|
||||||
|
{
|
||||||
|
if (m_index < BUFFER_SIZE)
|
||||||
|
m_frame[m_index++] = byte;
|
||||||
|
else
|
||||||
|
m_overflow = true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<size_t BUFFER_SIZE>
|
||||||
|
inline void FramePacker<BUFFER_SIZE>::packCRC(const uint16_t crc)
|
||||||
|
{
|
||||||
|
m_CRCSize = 0;
|
||||||
|
const uint8_t loCRC = static_cast<uint8_t>(crc & 0x00FF);
|
||||||
|
const uint8_t hiCRC = static_cast<uint8_t>((crc >> 8) & 0x00FF);
|
||||||
|
|
||||||
|
if (loCRC == SOF1_ || loCRC == SOF2_ || loCRC == EOF_ || loCRC == ESC_)
|
||||||
|
m_CRCBuffer[m_CRCSize++] = ESC_;
|
||||||
|
m_CRCBuffer[m_CRCSize++] = loCRC;
|
||||||
|
|
||||||
|
if (hiCRC == SOF1_ || hiCRC == SOF2_ || hiCRC == EOF_ || hiCRC == ESC_)
|
||||||
|
m_CRCBuffer[m_CRCSize++] = ESC_;
|
||||||
|
m_CRCBuffer[m_CRCSize++] = hiCRC;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_ToolBox::comm::uBinaryFrame
|
||||||
84
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/console/ansi.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::console::ANSI
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class EscapeCommand
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Color : uint8_t
|
||||||
|
{
|
||||||
|
Black = 0,
|
||||||
|
Red,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
Blue,
|
||||||
|
Magenta,
|
||||||
|
Cyan,
|
||||||
|
White,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint8_t LIGHT = 0x01 << 0; // 0x01
|
||||||
|
static constexpr uint8_t BRIGHT = 0x01 << 1; // 0x02
|
||||||
|
static constexpr uint8_t DIM = 0x01 << 2; // 0x04
|
||||||
|
static constexpr uint8_t UNDERLINE = 0x01 << 3; // 0x08
|
||||||
|
static constexpr uint8_t BLINK = 0x01 << 4; // 0x10
|
||||||
|
static constexpr uint8_t REVERSE = 0x01 << 5; // 0x20
|
||||||
|
|
||||||
|
public:
|
||||||
|
static std::string clear();
|
||||||
|
static std::string get(const Color foregroundColor,
|
||||||
|
const uint8_t foregroundStyle = 0,
|
||||||
|
const Color backgroundColor = Color::Black,
|
||||||
|
const uint8_t backgroundStyle = 0);
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline std::string EscapeCommand::clear()
|
||||||
|
{
|
||||||
|
return "\x1b[0m";
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline std::string EscapeCommand::get(const Color foregroundColor, const uint8_t foregroundStyle, const Color backgroundColor, const uint8_t backgroundStyle)
|
||||||
|
{
|
||||||
|
std::string command = "\x1b[";
|
||||||
|
int foregroundColorValue = static_cast<int>(foregroundColor) + 30;
|
||||||
|
if (foregroundStyle & LIGHT)
|
||||||
|
foregroundColorValue += 60;
|
||||||
|
command += std::to_string(foregroundColorValue) + ";";
|
||||||
|
|
||||||
|
int backgroundColorValue = static_cast<int>(backgroundColor) + 40;
|
||||||
|
if (backgroundStyle & LIGHT)
|
||||||
|
backgroundColorValue += 60;
|
||||||
|
command += std::to_string(backgroundColorValue);
|
||||||
|
|
||||||
|
if (foregroundStyle & BRIGHT)
|
||||||
|
command += ";1";
|
||||||
|
if (foregroundStyle & DIM)
|
||||||
|
command += ";2";
|
||||||
|
if (foregroundStyle & UNDERLINE)
|
||||||
|
command += ";4";
|
||||||
|
if (foregroundStyle & BLINK)
|
||||||
|
command += ";5";
|
||||||
|
if (foregroundStyle & REVERSE)
|
||||||
|
command += ";7";
|
||||||
|
|
||||||
|
command += "m";
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::console::ANSI
|
||||||
152
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/console/consoleTable.h
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::console
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ConsoleTable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Row = std::vector<std::string>;
|
||||||
|
using ColumnWidths = std::vector<size_t>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ConsoleTable() = default; // Constructor
|
||||||
|
virtual ~ConsoleTable() = default; // Destructor
|
||||||
|
ConsoleTable(const ConsoleTable &other) = delete; // Copy constructor
|
||||||
|
ConsoleTable(ConsoleTable &&other) noexcept = delete; // Move constructor
|
||||||
|
ConsoleTable &operator=(const ConsoleTable &other) = delete; // Copy assignment
|
||||||
|
ConsoleTable &operator=(ConsoleTable &&other) noexcept = delete; // Move assignment
|
||||||
|
|
||||||
|
void setHeaders(const Row &headers); // Set table headers
|
||||||
|
void addRow(const Row &row); // Add a row to the table
|
||||||
|
|
||||||
|
[[nodiscard]] std::string render() const; // Render the table as a string
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Row m_headers; // Table headers
|
||||||
|
std::vector<Row> m_rows; // Table rows
|
||||||
|
size_t m_padding = 2; // Padding between columns
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] ColumnWidths calculateColumnWidths() const; // Calculate column widths
|
||||||
|
[[nodiscard]] static std::string drawSeparator(const ColumnWidths &columnWidths); // Draw a separator line (+---+---+---+)
|
||||||
|
[[nodiscard]] static std::string drawRow(const Row &row, // Draw a single row
|
||||||
|
const ColumnWidths &columnWidths);
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set table headers */
|
||||||
|
inline void ConsoleTable::setHeaders(const Row &headers)
|
||||||
|
{
|
||||||
|
m_headers = headers;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Add a row to the table */
|
||||||
|
inline void ConsoleTable::addRow(const Row &row)
|
||||||
|
{
|
||||||
|
m_rows.push_back(row);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Render the table as a string */
|
||||||
|
inline std::string ConsoleTable::render() const
|
||||||
|
{
|
||||||
|
// Calculate column widths
|
||||||
|
const auto columnWidths = calculateColumnWidths();
|
||||||
|
|
||||||
|
// Initialize the output string
|
||||||
|
std::string output;
|
||||||
|
|
||||||
|
// Draw the top separator
|
||||||
|
output += drawSeparator(columnWidths);
|
||||||
|
|
||||||
|
// Draw the headers
|
||||||
|
output += drawRow(m_headers, columnWidths);
|
||||||
|
|
||||||
|
// Draw the separator between headers and data
|
||||||
|
output += drawSeparator(columnWidths);
|
||||||
|
|
||||||
|
// Draw the data rows
|
||||||
|
for (const auto &row : m_rows)
|
||||||
|
{
|
||||||
|
output += drawRow(row, columnWidths);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the bottom separator
|
||||||
|
output += drawSeparator(columnWidths);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Calculate column widths */
|
||||||
|
inline ConsoleTable::ColumnWidths ConsoleTable::calculateColumnWidths() const
|
||||||
|
{
|
||||||
|
// Determine the number of columns (based on headers or first row)
|
||||||
|
const auto numColumns = m_headers.empty() ? (m_rows.empty() ? 0 : m_rows[0].size()) : m_headers.size();
|
||||||
|
|
||||||
|
// Initialize column widths
|
||||||
|
ColumnWidths widths(numColumns, 0);
|
||||||
|
|
||||||
|
// Calculate widths based on headers
|
||||||
|
for (size_t i = 0; i < m_headers.size() && i < numColumns; ++i)
|
||||||
|
{
|
||||||
|
widths[i] = std::max(widths[i], m_headers[i].length() + m_padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate widths based on rows
|
||||||
|
for (const auto &row : m_rows)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < row.size() && i < numColumns; ++i)
|
||||||
|
{
|
||||||
|
widths[i] = std::max(widths[i], row[i].length() + m_padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widths;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Draw a separator line (+---+---+---+) */
|
||||||
|
inline std::string ConsoleTable::drawSeparator(const ColumnWidths &columnWidths)
|
||||||
|
{
|
||||||
|
std::string output;
|
||||||
|
output += "+";
|
||||||
|
for (const auto &width : columnWidths)
|
||||||
|
{
|
||||||
|
output += std::string(width, '-') + "+";
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Draw a single row */
|
||||||
|
inline std::string ConsoleTable::drawRow(const Row &row, const ColumnWidths &columnWidths)
|
||||||
|
{
|
||||||
|
std::string output;
|
||||||
|
output += "|";
|
||||||
|
for (size_t i = 0; i < columnWidths.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto cellContent = (i < row.size()) ? row[i] : "";
|
||||||
|
output += " " + cellContent;
|
||||||
|
// Add padding spaces
|
||||||
|
const size_t paddingSpaces = columnWidths[i] - cellContent.length() - 1;
|
||||||
|
output += std::string(paddingSpaces, ' ');
|
||||||
|
output += "|";
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::console
|
||||||
124
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/console/msw/win32Console.h
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <Windows.h>
|
||||||
|
#endif // defined WIN32
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::logs
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Win32Console
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void initConsole(); // Init application console and attach debug console under MSW
|
||||||
|
static void releaseConsole(); // Release application console
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~Win32Console(); // Default destructor
|
||||||
|
Win32Console(const Win32Console &obj) = delete; // Copy constructor
|
||||||
|
Win32Console(Win32Console &&obj) noexcept = delete; // Move constructor
|
||||||
|
Win32Console &operator=(const Win32Console &obj) = delete; // Copy assignment operator
|
||||||
|
Win32Console &operator=(Win32Console &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
static bool hasAttachedConsole(); // Returns true if console is attached, false otherwise
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static inline std::unique_ptr<Win32Console> m_singleton;
|
||||||
|
Win32Console(); // Default constructor
|
||||||
|
|
||||||
|
FILE *m_stdoutFile; // Reopened stdout file pointer
|
||||||
|
FILE *m_stderrFile; // Reopened stderr file pointer
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Init application console and attach debug console under MSW */
|
||||||
|
inline void Win32Console::initConsole()
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
# error This calss is only available under Windows systems
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!m_singleton)
|
||||||
|
m_singleton = std::unique_ptr<Win32Console>(new Win32Console());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Release application console */
|
||||||
|
inline void Win32Console::releaseConsole()
|
||||||
|
{
|
||||||
|
if (m_singleton)
|
||||||
|
m_singleton.reset();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
inline Win32Console::Win32Console()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
bool consoleIsCreated = false;
|
||||||
|
|
||||||
|
// Try to attach application to the current console
|
||||||
|
AttachConsole(ATTACH_PARENT_PROCESS);
|
||||||
|
|
||||||
|
if (!GetConsoleWindow()) // No console was available
|
||||||
|
{
|
||||||
|
// Create console and attach application to it
|
||||||
|
if (!AllocConsole())
|
||||||
|
throw std::logic_error("Unable to attach application to debug console"); // Error during creating console
|
||||||
|
consoleIsCreated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen stdout and stderr streams to console window
|
||||||
|
if (freopen_s(&m_stdoutFile, "CONOUT$", "w", stdout) != 0)
|
||||||
|
throw std::logic_error("Unable to reopen stdout"); // Error during reopen on stdout
|
||||||
|
|
||||||
|
if (freopen_s(&m_stderrFile, "CONOUT$", "w", stderr) != 0)
|
||||||
|
throw std::logic_error("Unable to reopen stderr"); // Error during reopen on stderr
|
||||||
|
|
||||||
|
std::cout.clear();
|
||||||
|
std::cerr.clear();
|
||||||
|
|
||||||
|
if (!consoleIsCreated)
|
||||||
|
std::cout << std::endl; // Add a new line if console was already existing
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
inline Win32Console::~Win32Console()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::cout.clear();
|
||||||
|
std::cerr.clear();
|
||||||
|
|
||||||
|
// Free console
|
||||||
|
FreeConsole();
|
||||||
|
|
||||||
|
// Close reopened stdout and stderr streams
|
||||||
|
(void)fclose(m_stdoutFile);
|
||||||
|
(void)fclose(m_stderrFile);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Returns true if console is attached, false otherwise */
|
||||||
|
inline bool Win32Console::hasAttachedConsole()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (GetConsoleWindow())
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::logs
|
||||||
81
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/crypto/base64.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::crypto
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Base64
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Base64() = default; // Default constructor
|
||||||
|
virtual ~Base64() = default; // Default destructor
|
||||||
|
Base64(const Base64 &obj) = delete; // Copy constructor
|
||||||
|
Base64(Base64 &&obj) noexcept = delete; // Move constructor
|
||||||
|
Base64 &operator=(const Base64 &obj) = delete; // Copy assignment operator
|
||||||
|
Base64 &operator=(Base64 &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
static std::string encode(const std::span<uint8_t> &binary_data); // Encodes binary data into a Base64 string
|
||||||
|
static std::vector<uint8_t> decode(const std::string &base64_string); // Decodes a Base64 string into binary data
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Encodes binary data into a Base64 string */
|
||||||
|
inline std::string Base64::encode(const std::span<uint8_t> &binary_data)
|
||||||
|
{
|
||||||
|
if (binary_data.empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// Calculate the length of the encoded data
|
||||||
|
const size_t output_len_max = EVP_ENCODE_LENGTH(binary_data.size());
|
||||||
|
|
||||||
|
// Allocate output buffer (with null terminator)
|
||||||
|
std::vector<char> output_buffer(output_len_max + 1, 0);
|
||||||
|
|
||||||
|
// OpenSSL encoding
|
||||||
|
const auto final_len = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(output_buffer.data()),
|
||||||
|
binary_data.data(),
|
||||||
|
binary_data.size());
|
||||||
|
if (final_len < 0)
|
||||||
|
throw std::runtime_error("Error: Base64 encoding failed");
|
||||||
|
|
||||||
|
return std::string(output_buffer.data());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Decodes a Base64 string into binary data */
|
||||||
|
inline std::vector<uint8_t> Base64::decode(const std::string &base64_string)
|
||||||
|
{
|
||||||
|
if (base64_string.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Calculate the length of the decoded data
|
||||||
|
const auto output_len_max = EVP_DECODE_LENGTH(base64_string.size());
|
||||||
|
|
||||||
|
// Allocate output buffer
|
||||||
|
std::vector<uint8_t> output_buffer(output_len_max);
|
||||||
|
|
||||||
|
// OpenSSL decoding
|
||||||
|
const auto final_len = EVP_DecodeBlock(output_buffer.data(),
|
||||||
|
reinterpret_cast<const unsigned char *>(base64_string.data()),
|
||||||
|
static_cast<int>(base64_string.size()));
|
||||||
|
if (final_len < 0)
|
||||||
|
throw std::runtime_error("Error: Base64 decoding failed: Invalid data");
|
||||||
|
output_buffer.resize(final_len);
|
||||||
|
|
||||||
|
return output_buffer;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::crypto
|
||||||
71
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/crypto/data.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::crypto
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Data8
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Data8() = default; // Default constructor
|
||||||
|
explicit Data8(const std::string &data); // Constructor from string
|
||||||
|
explicit Data8(const std::span<const uint8_t> &data); // Constructor from byte span
|
||||||
|
virtual ~Data8() = default; // Default destructor
|
||||||
|
Data8(const Data8 &obj) = delete; // Copy constructor
|
||||||
|
Data8(Data8 &&obj) noexcept = delete; // Move constructor
|
||||||
|
Data8 &operator=(const Data8 &obj) = delete; // Copy assignment operator
|
||||||
|
Data8 &operator=(Data8 &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
[[nodiscard]] std::string getString() const; // Get data as string
|
||||||
|
[[nodiscard]] std::span<const uint8_t> getBytes() const; // Get data as byte span
|
||||||
|
[[nodiscard]] size_t getSize() const; // Get data size
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<uint8_t> m_data; // Raw data storage
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor from string */
|
||||||
|
inline Data8::Data8(const std::string &data)
|
||||||
|
{
|
||||||
|
m_data = std::vector<uint8_t>(data.begin(), data.end());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline Data8::Data8(const std::span<const uint8_t> &data)
|
||||||
|
{
|
||||||
|
m_data = std::vector<uint8_t>(data.begin(), data.end());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get data as string */
|
||||||
|
inline std::string Data8::getString() const
|
||||||
|
{
|
||||||
|
return std::string(m_data.begin(), m_data.end());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get data as byte span */
|
||||||
|
inline std::span<const uint8_t> Data8::getBytes() const
|
||||||
|
{
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get data size */
|
||||||
|
inline size_t Data8::getSize() const
|
||||||
|
{
|
||||||
|
return m_data.size();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::crypto
|
||||||
78
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/crypto/digitalHash.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::crypto
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class DigitalHash
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DigitalHash() = default; // Default constructor
|
||||||
|
virtual ~DigitalHash() = default; // Default destructor
|
||||||
|
DigitalHash(const DigitalHash &obj) = delete; // Copy constructor
|
||||||
|
DigitalHash(DigitalHash &&obj) noexcept = delete; // Move constructor
|
||||||
|
DigitalHash &operator=(const DigitalHash &obj) = delete; // Copy assignment operator
|
||||||
|
DigitalHash &operator=(DigitalHash &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
static std::string calculateDataHash(const std::span<const uint8_t> &data, const EVP_MD *md_type); // Calculate digital hash from data buffer
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Calculate digital hash from data buffer */
|
||||||
|
inline std::string DigitalHash::calculateDataHash(const std::span<const uint8_t> &data, const EVP_MD *md_type)
|
||||||
|
{
|
||||||
|
// Initialize OpenSSL digest context
|
||||||
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
|
||||||
|
if (!mdctx)
|
||||||
|
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
|
||||||
|
|
||||||
|
// Initialize digest operation
|
||||||
|
if (EVP_DigestInit_ex(mdctx, md_type, nullptr) != 1)
|
||||||
|
{
|
||||||
|
EVP_MD_CTX_free(mdctx);
|
||||||
|
throw std::runtime_error("Error: EVP_DigestInit_ex failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update digest with data
|
||||||
|
if (EVP_DigestUpdate(mdctx, data.data(), data.size()) != 1)
|
||||||
|
{
|
||||||
|
EVP_MD_CTX_free(mdctx);
|
||||||
|
throw std::runtime_error("Error: EVP_DigestUpdate failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize digest computation
|
||||||
|
std::vector<uint8_t> hash(EVP_MD_size(md_type));
|
||||||
|
unsigned int hash_len = 0;
|
||||||
|
if (EVP_DigestFinal_ex(mdctx, hash.data(), &hash_len) != 1)
|
||||||
|
{
|
||||||
|
EVP_MD_CTX_free(mdctx);
|
||||||
|
throw std::runtime_error("Error: EVP_DigestFinal_ex failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
EVP_MD_CTX_free(mdctx);
|
||||||
|
|
||||||
|
// Convert hash to hexadecimal string
|
||||||
|
std::string hash_hex;
|
||||||
|
for (unsigned int i = 0; i < hash_len; ++i)
|
||||||
|
hash_hex += std::format("{:02x}", hash[i]);
|
||||||
|
|
||||||
|
return hash_hex;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::crypto
|
||||||
255
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/crypto/digitalSign.h
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "data.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::crypto
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class DigitalSign
|
||||||
|
{
|
||||||
|
// Custom deleter for EVP_PKEY to ensure it's automatically freed
|
||||||
|
struct EVP_PKEY_Deleter
|
||||||
|
{
|
||||||
|
void operator()(EVP_PKEY *p) const
|
||||||
|
{
|
||||||
|
if (p)
|
||||||
|
{
|
||||||
|
EVP_PKEY_free(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using pEVP_PKEY = std::unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
|
||||||
|
|
||||||
|
struct BIO_Deleter
|
||||||
|
{
|
||||||
|
void operator()(BIO *b) const
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
{
|
||||||
|
BIO_free(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using pBIO = std::unique_ptr<BIO, BIO_Deleter>;
|
||||||
|
|
||||||
|
struct EVP_MD_CTX_Deleter
|
||||||
|
{
|
||||||
|
void operator()(EVP_MD_CTX *ctx) const
|
||||||
|
{
|
||||||
|
if (ctx)
|
||||||
|
{
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using pEVP_MD_CTX = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_Deleter>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DigitalSign() = default; // Default constructor
|
||||||
|
virtual ~DigitalSign() = default; // Default destructor
|
||||||
|
DigitalSign(const DigitalSign &obj) = delete; // Copy constructor
|
||||||
|
DigitalSign(DigitalSign &&obj) noexcept = delete; // Move constructor
|
||||||
|
DigitalSign &operator=(const DigitalSign &obj) = delete; // Copy assignment operator
|
||||||
|
DigitalSign &operator=(DigitalSign &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
void loadPEMKeyFromFile(const std::filesystem::path &filepath, bool is_private); // Load key from PEM file
|
||||||
|
void loadPEMKeyFromMemory(const std::string &key, bool is_private); // Load key from memory
|
||||||
|
|
||||||
|
std::vector<uint8_t> sign(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key
|
||||||
|
std::string signHex(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key
|
||||||
|
|
||||||
|
bool verify(const Data8 &data, const std::span<const uint8_t> &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key
|
||||||
|
bool verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key
|
||||||
|
|
||||||
|
protected:
|
||||||
|
pEVP_PKEY m_privateKey;
|
||||||
|
pEVP_PKEY m_publicKey;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<uint8_t> hexToBytes(const std::string &hexString); // Converts a hexadecimal string into a vector of bytes
|
||||||
|
static std::string bytesToHex(const std::span<const uint8_t> &bytes); // Converts a span of bytes into a hexadecimal string representation
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Load key from PEM file */
|
||||||
|
inline void DigitalSign::loadPEMKeyFromFile(const std::filesystem::path &filepath, const bool is_private)
|
||||||
|
{
|
||||||
|
std::ifstream file(filepath, std::ifstream::binary);
|
||||||
|
if (!file.is_open())
|
||||||
|
throw std::runtime_error("Error: Could not open file " + filepath.string());
|
||||||
|
|
||||||
|
const std::string key((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
loadPEMKeyFromMemory(key, is_private);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Load key from memory */
|
||||||
|
inline void DigitalSign::loadPEMKeyFromMemory(const std::string &key, const bool is_private)
|
||||||
|
{
|
||||||
|
if (key.empty())
|
||||||
|
throw std::runtime_error("Error: Input stream is empty or could not be read");
|
||||||
|
|
||||||
|
// Create a memory BIO (Basic Input/Output) for reading the key
|
||||||
|
const auto bio = static_cast<pBIO>(BIO_new_mem_buf(key.data(), static_cast<int>(key.size())));
|
||||||
|
if (!bio)
|
||||||
|
throw std::runtime_error("Error: Failed to create memory BIO");
|
||||||
|
|
||||||
|
// Use the BIO to read the key
|
||||||
|
if (is_private)
|
||||||
|
{
|
||||||
|
m_privateKey = static_cast<pEVP_PKEY>(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
|
||||||
|
if (!m_privateKey)
|
||||||
|
throw std::runtime_error("Error: Failed to read private key from memory buffer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_publicKey = static_cast<pEVP_PKEY>(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr));
|
||||||
|
if (!m_publicKey)
|
||||||
|
throw std::runtime_error("Error: Failed to read private key from memory buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Sign data with private key */
|
||||||
|
inline std::vector<uint8_t> DigitalSign::sign(const Data8 &data, const EVP_MD *digest_type) const
|
||||||
|
{
|
||||||
|
if (!m_privateKey)
|
||||||
|
throw std::runtime_error("Error: Private key is not loaded for signing");
|
||||||
|
|
||||||
|
// Initialize the context for signing
|
||||||
|
const auto md_ctx = static_cast<pEVP_MD_CTX>(EVP_MD_CTX_new());
|
||||||
|
if (!md_ctx)
|
||||||
|
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
|
||||||
|
|
||||||
|
// Set the context for signing with the private key and digest
|
||||||
|
if (EVP_DigestSignInit(md_ctx.get(), nullptr, digest_type, nullptr, m_privateKey.get()) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestSignInit failed");
|
||||||
|
|
||||||
|
// Provide the data to be signed
|
||||||
|
if (EVP_DigestSignUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestSignUpdate failed");
|
||||||
|
|
||||||
|
// Determine the required signature size
|
||||||
|
size_t sigLen = 0;
|
||||||
|
if (EVP_DigestSignFinal(md_ctx.get(), nullptr, &sigLen) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestSignFinal (size) failed");
|
||||||
|
|
||||||
|
// Allocate space and generate the signature
|
||||||
|
std::vector<uint8_t> signature(sigLen);
|
||||||
|
if (EVP_DigestSignFinal(md_ctx.get(), signature.data(), &sigLen) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestSignFinal (signature) failed");
|
||||||
|
|
||||||
|
// Resize signature to the actual length used
|
||||||
|
signature.resize(sigLen);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Sign data */
|
||||||
|
inline std::string DigitalSign::signHex(const Data8 &data, const EVP_MD *digest_type) const
|
||||||
|
{
|
||||||
|
return bytesToHex(sign(data, digest_type));
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Verify signature with public key */
|
||||||
|
inline bool DigitalSign::verify(const Data8 &data, const std::span<const uint8_t> &signature, const EVP_MD *digest_type) const
|
||||||
|
{
|
||||||
|
if (!m_publicKey)
|
||||||
|
throw std::runtime_error("Error: Public key is not loaded for verifying");
|
||||||
|
|
||||||
|
// Initialize the context for verifying
|
||||||
|
const auto md_ctx = static_cast<pEVP_MD_CTX>(EVP_MD_CTX_new());
|
||||||
|
if (!md_ctx)
|
||||||
|
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
|
||||||
|
|
||||||
|
// Set the context for verifying with the public key and digest
|
||||||
|
if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, digest_type, nullptr, m_publicKey.get()) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestVerifyInit failed");
|
||||||
|
|
||||||
|
// Provide the original data
|
||||||
|
if (EVP_DigestVerifyUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1)
|
||||||
|
throw std::runtime_error("Error: EVP_DigestVerifyUpdate failed");
|
||||||
|
|
||||||
|
// Verify the signature
|
||||||
|
// Returns 1 for a good signature, 0 for a bad signature, and -1 on error
|
||||||
|
const auto result = EVP_DigestVerifyFinal(md_ctx.get(), signature.data(), signature.size());
|
||||||
|
if (result == 1)
|
||||||
|
return true; // Signature is valid
|
||||||
|
if (result == 0)
|
||||||
|
return false; // Signature is NOT valid
|
||||||
|
|
||||||
|
throw std::runtime_error("Error: EVP_DigestVerifyFinal failed during execution");
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Verify signature with public key */
|
||||||
|
inline bool DigitalSign::verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type) const
|
||||||
|
{
|
||||||
|
return verify(data, hexToBytes(signature), digest_type);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Converts a hexadecimal string into a vector of bytes */
|
||||||
|
inline std::vector<uint8_t> DigitalSign::hexToBytes(const std::string &hexString)
|
||||||
|
{
|
||||||
|
// A valid hex string must have an even length
|
||||||
|
if (hexString.length() % 2 != 0)
|
||||||
|
throw std::invalid_argument("Hex string must have an even length");
|
||||||
|
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.reserve(hexString.length() / 2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < hexString.length(); i += 2)
|
||||||
|
{
|
||||||
|
std::string byteString = hexString.substr(i, 2);
|
||||||
|
unsigned long byteVal;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byteVal = std::stoul(byteString, nullptr, 16);
|
||||||
|
}
|
||||||
|
catch (const std::exception & /*e*/)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Invalid hexadecimal character sequence: " + byteString); // Non-hex characters ?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the value fits in a uint8_t (should always be true for 2 chars)
|
||||||
|
if (byteVal > 0xFF)
|
||||||
|
throw std::invalid_argument("Internal error: Converted value exceeds 0xFF");
|
||||||
|
|
||||||
|
bytes.push_back(static_cast<uint8_t>(byteVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Converts a span of bytes into a hexadecimal string representation */
|
||||||
|
inline std::string DigitalSign::bytesToHex(const std::span<const uint8_t> &bytes)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::uppercase << std::setfill('0');
|
||||||
|
|
||||||
|
for (const uint8_t byte : bytes)
|
||||||
|
ss << std::format("{0:02X}", byte);
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::crypto
|
||||||
175
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/dateTime/age.h
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Age
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Age() = default; // Default constructor
|
||||||
|
virtual ~Age() = default; // Default destructor
|
||||||
|
Age(const Age &obj) = default; // Copy constructor
|
||||||
|
Age(Age &&obj) noexcept = default; // Move constructor
|
||||||
|
Age &operator=(const Age &obj) = default; // Copy assignment operator
|
||||||
|
Age &operator=(Age &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Age(std::chrono::nanoseconds age); // Constructor
|
||||||
|
void set(std::chrono::nanoseconds age); // Convert age in microseconds to days, hours, minutes, seconds, milliseconds and microseconds
|
||||||
|
|
||||||
|
std::chrono::microseconds getAge() const; // Return age in microseconds
|
||||||
|
std::chrono::days getDays() const; // Return days part in age
|
||||||
|
std::chrono::hours getHours() const; // Return hours part in age
|
||||||
|
std::chrono::minutes getMinutes() const; // Return minutes part in age
|
||||||
|
std::chrono::seconds getSeconds() const; // Return seconds part in age
|
||||||
|
std::chrono::milliseconds getMilliseconds() const; // Return milliseconds part in age
|
||||||
|
std::chrono::microseconds getMicroseconds() const; // Return microseconds part in age
|
||||||
|
std::chrono::nanoseconds getNanoseconds() const; // Return nanoseconds part in age
|
||||||
|
|
||||||
|
std::string toString(bool show_ms = true, bool show_us = false, bool show_ns = false) const; // Return age as a string in format "[d] H:m:s.ms.us.ns"
|
||||||
|
|
||||||
|
void testAge() const; // Test function to verify the correctness of the Age class implementation
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::chrono::nanoseconds m_age = std::chrono::nanoseconds(0);
|
||||||
|
|
||||||
|
std::chrono::days m_days = std::chrono::days(0);
|
||||||
|
std::chrono::hours m_hours = std::chrono::hours(0);
|
||||||
|
std::chrono::minutes m_minutes = std::chrono::minutes(0);
|
||||||
|
std::chrono::seconds m_seconds = std::chrono::seconds(0);
|
||||||
|
std::chrono::milliseconds m_milliseconds = std::chrono::milliseconds(0);
|
||||||
|
std::chrono::microseconds m_microseconds = std::chrono::microseconds(0);
|
||||||
|
std::chrono::nanoseconds m_nanoseconds = std::chrono::nanoseconds(0);
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline Age::Age(const std::chrono::nanoseconds age)
|
||||||
|
{
|
||||||
|
set(age);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Convert age in microseconds to days, hours, minutes, seconds, milliseconds and microseconds */
|
||||||
|
inline void Age::set(std::chrono::nanoseconds age)
|
||||||
|
{
|
||||||
|
constexpr auto max_ns = std::chrono::nanoseconds::max().count(); // For an int64_t storage, the maximum value corresponds to more than 106752 days
|
||||||
|
constexpr auto min_ns = std::chrono::nanoseconds::min().count();
|
||||||
|
if (age.count() > max_ns || age.count() < min_ns)
|
||||||
|
throw std::overflow_error("Duration exceeds the limits of std::chrono::nanoseconds");
|
||||||
|
|
||||||
|
if (age < std::chrono::nanoseconds(0))
|
||||||
|
throw std::invalid_argument("Age cannot be negative");
|
||||||
|
|
||||||
|
m_age = age;
|
||||||
|
|
||||||
|
m_days = std::chrono::duration_cast<std::chrono::days>(age);
|
||||||
|
age -= m_days;
|
||||||
|
|
||||||
|
m_hours = std::chrono::duration_cast<std::chrono::hours>(age);
|
||||||
|
age -= m_hours;
|
||||||
|
|
||||||
|
m_minutes = std::chrono::duration_cast<std::chrono::minutes>(age);
|
||||||
|
age -= m_minutes;
|
||||||
|
|
||||||
|
m_seconds = std::chrono::duration_cast<std::chrono::seconds>(age);
|
||||||
|
age -= m_seconds;
|
||||||
|
|
||||||
|
m_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(age);
|
||||||
|
age -= m_milliseconds;
|
||||||
|
|
||||||
|
m_microseconds = std::chrono::duration_cast<std::chrono::microseconds>(age);
|
||||||
|
age -= m_microseconds;
|
||||||
|
|
||||||
|
m_nanoseconds = age;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return age in microseconds */
|
||||||
|
inline std::chrono::microseconds Age::getAge() const
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<std::chrono::microseconds>(m_age);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return days part in age */
|
||||||
|
inline std::chrono::days Age::getDays() const
|
||||||
|
{
|
||||||
|
return m_days;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return hours part in age */
|
||||||
|
inline std::chrono::hours Age::getHours() const
|
||||||
|
{
|
||||||
|
return m_hours;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return minutes part in age */
|
||||||
|
inline std::chrono::minutes Age::getMinutes() const
|
||||||
|
{
|
||||||
|
return m_minutes;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return seconds part in age */
|
||||||
|
inline std::chrono::seconds Age::getSeconds() const
|
||||||
|
{
|
||||||
|
return m_seconds;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return milliseconds part in age */
|
||||||
|
inline std::chrono::milliseconds Age::getMilliseconds() const
|
||||||
|
{
|
||||||
|
return m_milliseconds;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return microseconds part in age */
|
||||||
|
inline std::chrono::microseconds Age::getMicroseconds() const
|
||||||
|
{
|
||||||
|
return m_microseconds;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return nanoseconds part in age */
|
||||||
|
inline std::chrono::nanoseconds Age::getNanoseconds() const
|
||||||
|
{
|
||||||
|
return m_nanoseconds;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return age as a string in format "[d] H:m:s.ms.us.ns".
|
||||||
|
* The show_ms, show_us and show_ns parameters control whether
|
||||||
|
* to include milliseconds, microseconds and nanoseconds in the
|
||||||
|
* output string. */
|
||||||
|
inline std::string Age::toString(const bool show_ms, const bool show_us, const bool show_ns) const
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
// Add days if greater than 0
|
||||||
|
if (m_days.count() > 0)
|
||||||
|
oss << std::format("[{}] ", m_days.count());
|
||||||
|
|
||||||
|
// Add hours, minutes and seconds
|
||||||
|
oss << std::format("{:02}:{:02}:{:02}", m_hours.count(), m_minutes.count(), m_seconds.count());
|
||||||
|
|
||||||
|
// Add milliseconds, microseconds and nanoseconds if requested
|
||||||
|
if (show_ms || show_us || show_ns)
|
||||||
|
oss << std::format(".{:03}", m_milliseconds.count());
|
||||||
|
if (show_us || show_ns)
|
||||||
|
oss << std::format("{:03}", m_microseconds.count());
|
||||||
|
if (show_ns)
|
||||||
|
oss << std::format("{:03}", m_nanoseconds.count());
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Test function to verify the correctness of the Age class implementation */
|
||||||
|
inline void Age::testAge() const
|
||||||
|
{
|
||||||
|
const Age age(std::chrono::nanoseconds(90061001001)); // 1 day, 1 hour, 1 minute, 1 second, 1 millisecond, 1 microsecond, 1 nanosecond
|
||||||
|
|
||||||
|
assert(age.getDays().count() == 1);
|
||||||
|
assert(age.getHours().count() == 1);
|
||||||
|
assert(age.getMinutes().count() == 1);
|
||||||
|
assert(age.getSeconds().count() == 1);
|
||||||
|
assert(age.getMilliseconds().count() == 1);
|
||||||
|
assert(age.getMicroseconds().count() == 1);
|
||||||
|
assert(age.getNanoseconds().count() == 1);
|
||||||
|
|
||||||
|
std::cout << "All tests passed!" << std::endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
49
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/dateTime/iPause.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::dateTime
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Abstract interface for pause functionality.
|
||||||
|
*
|
||||||
|
* Provides a common interface for implementing pause or delay mechanisms.
|
||||||
|
* Derived classes must implement the wait() method to pause execution
|
||||||
|
* for a specified duration.
|
||||||
|
*/
|
||||||
|
class IPause
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Duration = std::chrono::milliseconds;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~IPause() = default; // Default destructor
|
||||||
|
IPause(const IPause &obj) = default; // Copy constructor
|
||||||
|
IPause(IPause &&obj) noexcept = default; // Move constructor
|
||||||
|
IPause &operator=(const IPause &obj) = default; // Copy assignment operator
|
||||||
|
IPause &operator=(IPause &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pause execution for the specified duration.
|
||||||
|
* @param duration Duration of the pause.
|
||||||
|
*/
|
||||||
|
virtual void wait(const Duration &duration) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
IPause() = default; // Default constructor
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::dateTime
|
||||||
100
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/dateTime/iTimer.h
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::dateTime
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Abstract interface for timer functionality.
|
||||||
|
*
|
||||||
|
* Provides a common interface for timer implementations, allowing
|
||||||
|
* measurement of elapsed time and checking for timeouts.
|
||||||
|
* Derived classes must implement the now() method to provide the
|
||||||
|
* current time point.
|
||||||
|
*/
|
||||||
|
class ITimer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Clock = std::chrono::high_resolution_clock;
|
||||||
|
using TimePoint = Clock::time_point;
|
||||||
|
using Duration = std::chrono::milliseconds;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ITimer() = default; // Default destructor
|
||||||
|
ITimer(const ITimer &obj) = default; // Copy constructor
|
||||||
|
ITimer(ITimer &&obj) noexcept = default; // Move constructor
|
||||||
|
ITimer &operator=(const ITimer &obj) = default; // Copy assignment operator
|
||||||
|
ITimer &operator=(ITimer &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the reference time point to the current time.
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current time point.
|
||||||
|
* @return The current time point as defined by the derived class.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual TimePoint now() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the elapsed duration since the last reset.
|
||||||
|
* @return Duration since the last reset.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] Duration getElapsed() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the specified duration has elapsed since the last reset.
|
||||||
|
* @param duration The duration to check.
|
||||||
|
* @param autoReset If true, automatically resets the timer when the duration has elapsed.
|
||||||
|
* @return True if the duration has elapsed, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isElapsed(const Duration duration,
|
||||||
|
bool autoReset = false);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ITimer() = default; // Default constructor
|
||||||
|
|
||||||
|
TimePoint m_t0; // Timepoint t0 for elapsed time measurement
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Reset the timepoint t0 */
|
||||||
|
inline void ITimer::reset()
|
||||||
|
{
|
||||||
|
m_t0 = now();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the elapsed time */
|
||||||
|
inline ITimer::Duration ITimer::getElapsed() const
|
||||||
|
{
|
||||||
|
return std::chrono::duration_cast<Duration>(now() - m_t0);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if the specified delay has elapsed */
|
||||||
|
inline bool ITimer::isElapsed(const Duration duration, bool autoReset)
|
||||||
|
{
|
||||||
|
Duration elapsed = getElapsed();
|
||||||
|
if (elapsed >= duration)
|
||||||
|
{
|
||||||
|
if (autoReset)
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::dateTime
|
||||||
59
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/dateTime/pause.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "iPause.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::dateTime
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Standard pause implementation.
|
||||||
|
*
|
||||||
|
* Implements IPause to provide a blocking pause using standard C++ mechanisms.
|
||||||
|
* The actual delay is performed using std::this_thread::sleep_for.
|
||||||
|
*/
|
||||||
|
class Pause : public IPause
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Pause() = default; // Default constructor
|
||||||
|
~Pause() = default; // Default destructor
|
||||||
|
Pause(const Pause &obj) = default; // Copy constructor
|
||||||
|
Pause(Pause &&obj) noexcept = default; // Move constructor
|
||||||
|
Pause &operator=(const Pause &obj) = default; // Copy assignment operator
|
||||||
|
Pause &operator=(Pause &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Pause(const Duration &duration); // Constructor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pause execution for the specified duration.
|
||||||
|
* @param duration Duration of the pause.
|
||||||
|
*/
|
||||||
|
void wait(const Duration &duration) const override;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline Pause::Pause(const Duration &duration)
|
||||||
|
{
|
||||||
|
wait(duration);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Pause execution for the specified duration */
|
||||||
|
inline void Pause::wait(const Duration &duration) const
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::dateTime
|
||||||
55
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/dateTime/timer.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "iTimer.h"
|
||||||
|
|
||||||
|
namespace sdi_toolBox::dateTime
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Standard timer implementation using std::chrono.
|
||||||
|
*
|
||||||
|
* Implements ITimer using std::chrono::high_resolution_clock for
|
||||||
|
* high-precision timing in standard C++ environments.
|
||||||
|
*/
|
||||||
|
class Timer : public ITimer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Timer(); // Default constructor
|
||||||
|
~Timer() = default; // Default destructor
|
||||||
|
Timer(const Timer &obj) = default; // Copy constructor
|
||||||
|
Timer(Timer &&obj) noexcept = default; // Move constructor
|
||||||
|
Timer &operator=(const Timer &obj) = default; // Copy assignment operator
|
||||||
|
Timer &operator=(Timer &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current time point using std::chrono::high_resolution_clock.
|
||||||
|
* @return The current time point.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] TimePoint now() const override;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
inline Timer::Timer()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the current timepoint */
|
||||||
|
inline Timer::TimePoint Timer::now() const
|
||||||
|
{
|
||||||
|
return Clock::now();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::dateTime
|
||||||
24765
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/external/json/nlohmann/json.hpp
vendored
Normal file
176
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/external/json/nlohmann/json_fwd.hpp
vendored
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.11.3
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <map> // map
|
||||||
|
#include <memory> // allocator
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.11.3
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file contains all macro definitions affecting or depending on the ABI
|
||||||
|
|
||||||
|
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||||
|
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
|
||||||
|
#warning "Already included a different version of the library!"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTICS
|
||||||
|
#define JSON_DIAGNOSTICS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Construct the namespace ABI tags component
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
|
||||||
|
|
||||||
|
// Construct the namespace version component
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
_v ## major ## _ ## minor ## _ ## patch
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
||||||
|
|
||||||
|
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_MINOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Combine namespace components
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE \
|
||||||
|
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||||
|
namespace nlohmann \
|
||||||
|
{ \
|
||||||
|
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION) \
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_END \
|
||||||
|
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
||||||
|
} // namespace nlohmann
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief namespace for Niels Lohmann
|
||||||
|
@see https://github.com/nlohmann
|
||||||
|
@since version 1.0.0
|
||||||
|
*/
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default JSONSerializer template argument
|
||||||
|
|
||||||
|
This serializer ignores the template arguments and uses ADL
|
||||||
|
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
|
||||||
|
for serialization.
|
||||||
|
*/
|
||||||
|
template<typename T = void, typename SFINAE = void>
|
||||||
|
struct adl_serializer;
|
||||||
|
|
||||||
|
/// a class to store JSON values
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/
|
||||||
|
template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||||
|
std::map,
|
||||||
|
template<typename U, typename... Args> class ArrayType = std::vector,
|
||||||
|
class StringType = std::string, class BooleanType = bool,
|
||||||
|
class NumberIntegerType = std::int64_t,
|
||||||
|
class NumberUnsignedType = std::uint64_t,
|
||||||
|
class NumberFloatType = double,
|
||||||
|
template<typename U> class AllocatorType = std::allocator,
|
||||||
|
template<typename T, typename SFINAE = void> class JSONSerializer =
|
||||||
|
adl_serializer,
|
||||||
|
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
||||||
|
class CustomBaseClass = void>
|
||||||
|
class basic_json;
|
||||||
|
|
||||||
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||||
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||||
|
template<typename RefStringType>
|
||||||
|
class json_pointer;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default specialization
|
||||||
|
@sa https://json.nlohmann.me/api/json/
|
||||||
|
*/
|
||||||
|
using json = basic_json<>;
|
||||||
|
|
||||||
|
/// @brief a minimal map-like container that preserves insertion order
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_map/
|
||||||
|
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||||
|
struct ordered_map;
|
||||||
|
|
||||||
|
/// @brief specialization that maintains the insertion order of object keys
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_json/
|
||||||
|
using ordered_json = basic_json<nlohmann::ordered_map>;
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
105
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/generic/circularBuffer.h
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::generic
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
class CircularBuffer final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CircularBuffer() = default; // Constructor
|
||||||
|
~CircularBuffer() = default; // Destructor
|
||||||
|
CircularBuffer(const CircularBuffer &other) = delete; // Copy constructor
|
||||||
|
CircularBuffer(CircularBuffer &&other) noexcept = delete; // Move constructor
|
||||||
|
CircularBuffer &operator=(const CircularBuffer &other) = delete; // Copy assignment
|
||||||
|
CircularBuffer &operator=(CircularBuffer &&other) noexcept = delete; // Move assignment
|
||||||
|
|
||||||
|
bool push(const T &item); // Add an element to the buffer
|
||||||
|
std::optional<T> pop(); // Retrieve an element from the buffer
|
||||||
|
bool isEmpty() const; // Check if the buffer is empty
|
||||||
|
bool isFull() const; // Check if the buffer is full
|
||||||
|
size_t size() const; // Get the current number of elements
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<T, BUFFER_SIZE> m_buffer;
|
||||||
|
volatile size_t m_head = 0; // Index for the next write (push)
|
||||||
|
volatile size_t m_tail = 0; // Index for the next read (pop)
|
||||||
|
volatile size_t m_size = 0; // Current number of elements stored
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Add an element to the buffer */
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
inline bool CircularBuffer<T, BUFFER_SIZE>::push(const T &item)
|
||||||
|
{
|
||||||
|
if (isFull())
|
||||||
|
return false; // Buffer is full
|
||||||
|
|
||||||
|
// Write the item at the current head position
|
||||||
|
m_buffer[m_head] = item;
|
||||||
|
|
||||||
|
// Move head pointer, wrapping around using modulo
|
||||||
|
m_head = (m_head + 1) % BUFFER_SIZE;
|
||||||
|
|
||||||
|
// Increase the current size of the data in the buffer
|
||||||
|
m_size = m_size + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Retrieve an element from the buffer */
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
inline std::optional<T> CircularBuffer<T, BUFFER_SIZE>::pop()
|
||||||
|
{
|
||||||
|
if (isEmpty())
|
||||||
|
return {}; // Buffer is empty, cannot pop
|
||||||
|
|
||||||
|
// Read (copy) the item at the current tail position
|
||||||
|
T item = m_buffer[m_tail];
|
||||||
|
|
||||||
|
// Move tail pointer, wrapping around using modulo
|
||||||
|
m_tail = (m_tail + 1) % BUFFER_SIZE;
|
||||||
|
|
||||||
|
// Decrease the current size of the data in the buffer
|
||||||
|
m_size = m_size - 1;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if the buffer is empty */
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
inline bool CircularBuffer<T, BUFFER_SIZE>::isEmpty() const
|
||||||
|
{
|
||||||
|
return m_size == 0;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if the buffer is full */
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
inline bool CircularBuffer<T, BUFFER_SIZE>::isFull() const
|
||||||
|
{
|
||||||
|
return m_size == BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the current number of elements */
|
||||||
|
template<class T, size_t BUFFER_SIZE>
|
||||||
|
inline size_t CircularBuffer<T, BUFFER_SIZE>::size() const
|
||||||
|
{
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::generic
|
||||||
163
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/generic/crc.h
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace sdi_ToolBox::generic
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @class CRC16_modbus
|
||||||
|
* @brief CRC16 calculator for the Modbus protocol.
|
||||||
|
*
|
||||||
|
* Provides both static and incremental interfaces to compute the CRC16 checksum
|
||||||
|
* as used in the Modbus protocol (polynomial 0xA001, initial value 0xFFFF).
|
||||||
|
*
|
||||||
|
* Usage examples:
|
||||||
|
* @code
|
||||||
|
* // One-shot CRC calculation
|
||||||
|
* std::array<uint8_t, 8> data = { ... };
|
||||||
|
* uint16_t crc = Crc16_modbus::computeCRC(data);
|
||||||
|
*
|
||||||
|
* // Incremental CRC calculation
|
||||||
|
* Crc16_modbus crcHandle;
|
||||||
|
* crcHandle.init();
|
||||||
|
* for (uint8_t b : data) {
|
||||||
|
* crcHandle.update(b);
|
||||||
|
* }
|
||||||
|
* uint16_t crc = crcHandle.finalize();
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
class CRC16_modbus
|
||||||
|
{
|
||||||
|
static constexpr uint16_t INITIAL_VALUE = 0xFFFF;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Computes the CRC16 of the given data in one call.
|
||||||
|
* @param data The data buffer to compute the CRC for.
|
||||||
|
* @return The computed CRC16 value.
|
||||||
|
*/
|
||||||
|
static uint16_t computeCRC(const std::span<const uint8_t> &data);
|
||||||
|
|
||||||
|
public:
|
||||||
|
CRC16_modbus() = default; // Default constructor
|
||||||
|
virtual ~CRC16_modbus() = default; // Default destructor
|
||||||
|
CRC16_modbus(const CRC16_modbus &other) = default; // Copy constructor
|
||||||
|
CRC16_modbus(CRC16_modbus &&other) noexcept = default; // Move constructor
|
||||||
|
CRC16_modbus &operator=(const CRC16_modbus &other) = default; // Copy assignment
|
||||||
|
CRC16_modbus &operator=(CRC16_modbus &&other) noexcept = default; // Move assignment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs and initializes the CRC with the given data.
|
||||||
|
* @param data The data buffer to initialize and update the CRC with.
|
||||||
|
*/
|
||||||
|
explicit CRC16_modbus(const std::span<const uint8_t> &data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes or re-initializes the CRC to the default value (0xFFFF).
|
||||||
|
*/
|
||||||
|
void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the CRC with a single byte.
|
||||||
|
* @param val The byte value to update the CRC with.
|
||||||
|
*/
|
||||||
|
void update(const uint8_t &val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the CRC with a data buffer.
|
||||||
|
* @param data The data buffer to update the CRC with.
|
||||||
|
*/
|
||||||
|
void update(const std::span<const uint8_t> &data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finalizes the CRC calculation and returns the CRC value.
|
||||||
|
* @return The finalized CRC16 value.
|
||||||
|
*/
|
||||||
|
uint16_t finalize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current CRC value.
|
||||||
|
* @return The current CRC16 value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] uint16_t getCRC() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t m_crc = INITIAL_VALUE; // Current CRC value
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Compute CRC in one go */
|
||||||
|
inline uint16_t CRC16_modbus::computeCRC(const std::span<const uint8_t> &data)
|
||||||
|
{
|
||||||
|
const CRC16_modbus crc(data);
|
||||||
|
return crc.finalize();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline CRC16_modbus::CRC16_modbus(const std::span<const uint8_t> &data)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
update(data);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Reinit crc handle */
|
||||||
|
inline void CRC16_modbus::init()
|
||||||
|
{
|
||||||
|
m_crc = INITIAL_VALUE;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update CRC */
|
||||||
|
inline void CRC16_modbus::update(const uint8_t &val)
|
||||||
|
{
|
||||||
|
constexpr uint16_t PolynomialValue = 0xA001;
|
||||||
|
|
||||||
|
m_crc ^= static_cast<uint16_t>(val); // XOR byte into least sig. byte of crc
|
||||||
|
for (int i = 8; i != 0; i--) // Loop over each bit
|
||||||
|
{
|
||||||
|
if ((m_crc & 0x0001) != 0) // If the LSB is set
|
||||||
|
{
|
||||||
|
m_crc >>= 1; // Shift right and XOR PolynomialValue
|
||||||
|
m_crc ^= PolynomialValue;
|
||||||
|
}
|
||||||
|
else // Else LSB is not set
|
||||||
|
{
|
||||||
|
m_crc >>= 1; // Just shift right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update CRC */
|
||||||
|
inline void CRC16_modbus::update(const std::span<const uint8_t> &data)
|
||||||
|
{
|
||||||
|
for (const auto &c : data)
|
||||||
|
update(c);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Finalize and return CRC value */
|
||||||
|
inline uint16_t CRC16_modbus::finalize() const
|
||||||
|
{
|
||||||
|
return getCRC();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return CRC value */
|
||||||
|
inline uint16_t CRC16_modbus::getCRC() const
|
||||||
|
{
|
||||||
|
return m_crc;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_ToolBox::generic
|
||||||
79
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/generic/uuid.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/uuid/uuid.hpp> // Pour boost::uuids::uuid
|
||||||
|
#include <boost/uuid/uuid_generators.hpp> // Pour boost::uuids::random_generator
|
||||||
|
#include <boost/uuid/uuid_io.hpp> // Pour boost::uuids::to_string
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sdi_toolBox::generic
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class UuidGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::string uuid_v4(); // Generate a random UUID v4 string
|
||||||
|
static std::string uniqid(bool moreEntropy = false); // Generate a unique identifier string (as in PHP)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UuidGenerator() = default; // Default constructor
|
||||||
|
~UuidGenerator() = default; // Default destructor
|
||||||
|
UuidGenerator(const UuidGenerator &) = default; // Copy constructor
|
||||||
|
UuidGenerator(UuidGenerator &&) noexcept = default; // Move constructor
|
||||||
|
UuidGenerator &operator=(const UuidGenerator &) = default; // Copy assignment operator
|
||||||
|
UuidGenerator &operator=(UuidGenerator &&) noexcept = default; // Move assignment operator
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Generate a random UUID v4 string */
|
||||||
|
inline std::string UuidGenerator::uuid_v4()
|
||||||
|
{
|
||||||
|
boost::uuids::random_generator gen; // Create a random UUID generator
|
||||||
|
boost::uuids::uuid u = gen(); // Generate a random UUID
|
||||||
|
return boost::uuids::to_string(u); // Convert the UUID to a string and return it
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Generate a unique identifier string (as in PHP) */
|
||||||
|
inline std::string UuidGenerator::uniqid(const bool moreEntropy)
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now(); // Get current time point
|
||||||
|
const auto epoch = now.time_since_epoch(); // Get duration since epoch
|
||||||
|
const auto us_since_epoch = std::chrono::duration_cast<std::chrono::microseconds>(epoch).count();
|
||||||
|
|
||||||
|
// Format the time part into a hexadecimal string
|
||||||
|
const auto time_part = us_since_epoch / 16;
|
||||||
|
|
||||||
|
std::string result = std::format("{0:x}", time_part);
|
||||||
|
|
||||||
|
// PHP's uniqid pads the result to 13 characters if needed. We replicate the core logic.
|
||||||
|
// If the time part is shorter than 13 characters (highly unlikely today), it should be padded.
|
||||||
|
if (result.length() < 13)
|
||||||
|
result.insert(0, 13 - result.length(), '0');
|
||||||
|
|
||||||
|
// Add more entropy if requested (similar to PHP's second argument = true)
|
||||||
|
if (moreEntropy)
|
||||||
|
{
|
||||||
|
// Generate 5 bytes of random data (10 characters hex)
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937 generator(rd());
|
||||||
|
unsigned int rand_val = generator(); // Generate a random 32-bit number and use a portion of a second random number
|
||||||
|
|
||||||
|
// Append the random data in hexadecimal format
|
||||||
|
result = result + std::format(".{0:08x}", rand_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::generic
|
||||||
531
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/logs/reLog.h
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../console/ansi.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
# define WIN32_LEAN_AND_MEAN // Disable windows mess
|
||||||
|
# endif
|
||||||
|
# ifndef NOMINMAX
|
||||||
|
# define NOMINMAX
|
||||||
|
# endif
|
||||||
|
# include <Windows.h>
|
||||||
|
# undef GetMessage // To avoid conflict with other libraries
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <source_location>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Usage :
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* int value = 42;
|
||||||
|
* std::string user = "Alice";
|
||||||
|
*
|
||||||
|
* // Usage example:
|
||||||
|
* LogMessage() << "System ready" << std::endl;
|
||||||
|
* LogDebug() << "Checking configuration file..." << std::endl;
|
||||||
|
* LogInfo() << "User " << user << " logged in. (Value: " << value << ")" << std::endl;
|
||||||
|
* LogWarning() << "Device connection lost..." << std::endl;
|
||||||
|
* LogError() << "Failed to connect to server on port " << 8080 << "!" << std::endl;
|
||||||
|
* LogComm() << "<< 0123456789" << std::endl;
|
||||||
|
* LogDev() << "<< 0123456789" << std::endl;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace sdi_toolBox::logs
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class LogLevel : uint8_t
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Dev,
|
||||||
|
Comm,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
Message,
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ReLog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Log
|
||||||
|
{
|
||||||
|
LogLevel level;
|
||||||
|
std::chrono::system_clock::time_point timestamp;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ColorLogMap = std::map<LogLevel, std::string>;
|
||||||
|
using VisibleLogMap = std::map<LogLevel, bool>;
|
||||||
|
static VisibleLogMap getVisibleLogMap(); // Return the map of visible log messages
|
||||||
|
static void setVisibleLogMap(const VisibleLogMap &logMap); // Define the map of visible log messages
|
||||||
|
|
||||||
|
static ReLog &systemInit(const std::filesystem::path &filepath = ""); // Initialize log system
|
||||||
|
static ReLog &getInstance(); // Gets singleton instance
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~ReLog() = default; // Default destructor
|
||||||
|
ReLog(const ReLog &obj) = delete; // Copy constructor
|
||||||
|
ReLog(ReLog &&obj) noexcept = delete; // Move constructor
|
||||||
|
ReLog &operator=(const ReLog &obj) = delete; // Copy assignment operator
|
||||||
|
ReLog &operator=(ReLog &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
void openFile(const std::filesystem::path &filepath); // Open or create log file
|
||||||
|
void closeFile(); // Close log file
|
||||||
|
|
||||||
|
void log(const Log &log); // Core log method
|
||||||
|
|
||||||
|
void printColorTest() const; // Print the different configurations and colors on the terminal
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ReLog(); // Default constructor
|
||||||
|
|
||||||
|
bool m_vtModeEnabled = false;
|
||||||
|
|
||||||
|
std::mutex m_mtx;
|
||||||
|
std::ofstream m_file;
|
||||||
|
|
||||||
|
static inline ColorLogMap m_colorLogMap;
|
||||||
|
static inline VisibleLogMap m_visibleLogMap;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void enableVTMode();
|
||||||
|
|
||||||
|
static void init(); // Settings initialization
|
||||||
|
|
||||||
|
static std::string createLogMessage(const Log &log, const bool isFile = false); // Print log messages
|
||||||
|
void printDate();
|
||||||
|
|
||||||
|
void saveToFile(const std::string &message); // Save log messages in file
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return the map of visible log messages */
|
||||||
|
inline ReLog::VisibleLogMap ReLog::getVisibleLogMap()
|
||||||
|
{
|
||||||
|
return m_visibleLogMap;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Define the map of visible log messages */
|
||||||
|
inline void ReLog::setVisibleLogMap(const VisibleLogMap &logMap)
|
||||||
|
{
|
||||||
|
m_visibleLogMap = logMap;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Initialize log system */
|
||||||
|
inline ReLog &ReLog::systemInit(const std::filesystem::path &filepath)
|
||||||
|
{
|
||||||
|
auto &logSystem = getInstance();
|
||||||
|
|
||||||
|
// Post initialization
|
||||||
|
if (!filepath.empty())
|
||||||
|
logSystem.openFile(filepath);
|
||||||
|
logSystem.printDate();
|
||||||
|
return logSystem;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Gets singleton instance */
|
||||||
|
inline ReLog &ReLog::getInstance()
|
||||||
|
{
|
||||||
|
static ReLog instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline ReLog::ReLog()
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
init();
|
||||||
|
enableVTMode();
|
||||||
|
|
||||||
|
// Test on debug mode
|
||||||
|
#ifdef DEBUG
|
||||||
|
// printColorTest();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Open or create log file */
|
||||||
|
inline void ReLog::openFile(const std::filesystem::path &filepath)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
m_file = std::ofstream(filepath, std::ofstream::out | std::ofstream::app | std::ofstream::binary);
|
||||||
|
if (!m_file)
|
||||||
|
throw std::runtime_error("unable to open log file");
|
||||||
|
m_file << "\n-----------------------------------------------------------------" << std::endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Close log file */
|
||||||
|
inline void ReLog::closeFile()
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
if (m_file.is_open())
|
||||||
|
m_file.close();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Core log method */
|
||||||
|
inline void ReLog::log(const Log &log)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mtx);
|
||||||
|
|
||||||
|
const auto termMessage = createLogMessage(log);
|
||||||
|
if (!termMessage.empty())
|
||||||
|
{
|
||||||
|
if (log.level == LogLevel::Error)
|
||||||
|
std::cerr << termMessage << std::endl;
|
||||||
|
else
|
||||||
|
std::cout << termMessage << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fileMessage = createLogMessage(log, true);
|
||||||
|
if (!fileMessage.empty())
|
||||||
|
saveToFile(fileMessage);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Print the different configurations and colors on the terminal */
|
||||||
|
inline void ReLog::printColorTest() const
|
||||||
|
{
|
||||||
|
const auto coutTest = [&](const auto &color, const std::string &label)
|
||||||
|
{
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BLINK) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::REVERSE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << " | ";
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << " | ";
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
std::cout << std::endl;
|
||||||
|
};
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, " B ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, " R ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, " G ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, " Y ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, " B ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, " M ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, " C ");
|
||||||
|
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, " W ");
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline void ReLog::enableVTMode()
|
||||||
|
{
|
||||||
|
if (m_vtModeEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_vtModeEnabled = false;
|
||||||
|
|
||||||
|
// 1. Get the handle to the standard output
|
||||||
|
const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
if (hOut == INVALID_HANDLE_VALUE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2. Get the current console mode
|
||||||
|
DWORD dwMode = 0;
|
||||||
|
if (!GetConsoleMode(hOut, &dwMode))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 3. Set the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag (using bitwise OR)
|
||||||
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
|
||||||
|
// 4. Apply the new console mode
|
||||||
|
if (!SetConsoleMode(hOut, dwMode)) // This is often the point of failure on older Windows versions (pre-Win10 v1511)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_vtModeEnabled = true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Settings initialization */
|
||||||
|
inline void ReLog::init()
|
||||||
|
{
|
||||||
|
m_colorLogMap[LogLevel::None] = sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
m_colorLogMap[LogLevel::Dev] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::DIM);
|
||||||
|
m_colorLogMap[LogLevel::Comm] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta);
|
||||||
|
m_colorLogMap[LogLevel::Debug] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::DIM);
|
||||||
|
m_colorLogMap[LogLevel::Info] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White);
|
||||||
|
m_colorLogMap[LogLevel::Warning] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow);
|
||||||
|
m_colorLogMap[LogLevel::Error] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT);
|
||||||
|
m_colorLogMap[LogLevel::Message] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT);
|
||||||
|
|
||||||
|
m_visibleLogMap[LogLevel::Dev] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Comm] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Debug] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Info] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Warning] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Error] = true;
|
||||||
|
m_visibleLogMap[LogLevel::Message] = true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Print log messages */
|
||||||
|
inline std::string ReLog::createLogMessage(const Log &log, const bool isFile)
|
||||||
|
{
|
||||||
|
// Check if the message should be visible or not
|
||||||
|
if (m_visibleLogMap.contains(log.level) && m_visibleLogMap[log.level] == false)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::ostringstream stream;
|
||||||
|
|
||||||
|
// Prepare message color
|
||||||
|
const auto levelStyle = (m_colorLogMap.contains(log.level)) ? m_colorLogMap[log.level] : sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
const auto messageStyle = (!isFile) ? levelStyle : "";
|
||||||
|
|
||||||
|
if (!log.message.empty())
|
||||||
|
{
|
||||||
|
switch (log.level)
|
||||||
|
{
|
||||||
|
case LogLevel::Error:
|
||||||
|
stream << messageStyle << "[E] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Warning:
|
||||||
|
stream << messageStyle << "[W] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Info:
|
||||||
|
stream << messageStyle << "[I] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Debug:
|
||||||
|
stream << messageStyle << "[D] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Comm:
|
||||||
|
stream << messageStyle << "[C] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Dev:
|
||||||
|
stream << messageStyle << "[-] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Message:
|
||||||
|
default:;
|
||||||
|
stream << messageStyle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.level != LogLevel::Message)
|
||||||
|
{
|
||||||
|
const auto local_tz = std::chrono::current_zone();
|
||||||
|
const auto local_time = std::chrono::zoned_time(local_tz, log.timestamp);
|
||||||
|
stream << std::format("{0:%T} - ", local_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print message
|
||||||
|
stream << log.message;
|
||||||
|
|
||||||
|
// Reset style
|
||||||
|
if (!messageStyle.empty())
|
||||||
|
stream << sdi_toolBox::console::ANSI::EscapeCommand::clear();
|
||||||
|
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline void ReLog::printDate()
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
const auto local_tz = std::chrono::current_zone();
|
||||||
|
const auto local_time = std::chrono::zoned_time(local_tz, now);
|
||||||
|
|
||||||
|
const Log logMessage{ .level = LogLevel::Info,
|
||||||
|
.timestamp = now,
|
||||||
|
.message = std::format("current date: {0:%F}", local_time) };
|
||||||
|
log(logMessage);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Save log messages in file */
|
||||||
|
inline void ReLog::saveToFile(const std::string &message)
|
||||||
|
{
|
||||||
|
// Remove escape sequences
|
||||||
|
std::string messageResult;
|
||||||
|
bool in_escape_sequence = false;
|
||||||
|
|
||||||
|
constexpr char ANSI_ESCAPE_CHAR = '\x1b';
|
||||||
|
for (const char c : message)
|
||||||
|
{
|
||||||
|
if (in_escape_sequence)
|
||||||
|
{
|
||||||
|
if (c == 'm')
|
||||||
|
in_escape_sequence = false; // End of the escape sequence
|
||||||
|
}
|
||||||
|
else if (c == ANSI_ESCAPE_CHAR)
|
||||||
|
in_escape_sequence = true; // Begin of the escape sequence
|
||||||
|
else
|
||||||
|
messageResult += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_file.is_open())
|
||||||
|
m_file << messageResult << "\n";
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ReLogStreamProxy
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ReLogStreamProxy() = delete; // Constructor
|
||||||
|
explicit ReLogStreamProxy(const LogLevel &level); // Constructor
|
||||||
|
virtual ~ReLogStreamProxy(); // Destructor
|
||||||
|
ReLogStreamProxy(ReLogStreamProxy &obj) = delete; // Copy constructor
|
||||||
|
ReLogStreamProxy(ReLogStreamProxy &&obj) = delete; // Move constructor
|
||||||
|
ReLogStreamProxy &operator=(const ReLogStreamProxy &obj) = delete; // Copy assignment operator
|
||||||
|
ReLogStreamProxy &operator=(ReLogStreamProxy &&obj) = delete; // Move assignment operator
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
ReLogStreamProxy &operator<<(const T &data); // Overload for various data types (int, string, double, etc.)
|
||||||
|
ReLogStreamProxy &operator<<(std::ostream &(*manip)(std::ostream &)); // Overload for std::endl and other manipulators
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LogLevel m_level;
|
||||||
|
std::chrono::system_clock::time_point m_timestamp;
|
||||||
|
std::stringstream m_buffer;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flush(); // Pass the buffered content to the central Logger
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
inline ReLogStreamProxy::ReLogStreamProxy(const LogLevel &level)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
m_level = level;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Destructor */
|
||||||
|
inline ReLogStreamProxy::~ReLogStreamProxy()
|
||||||
|
{
|
||||||
|
if (!m_buffer.str().empty())
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Overload for various data types (int, string, double, etc.) */
|
||||||
|
template<typename T>
|
||||||
|
inline ReLogStreamProxy &ReLogStreamProxy::operator<<(const T &data)
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
if (m_buffer.str().empty())
|
||||||
|
m_timestamp = now;
|
||||||
|
|
||||||
|
m_buffer << data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Overload for std::endl and other manipulators */
|
||||||
|
inline ReLogStreamProxy &ReLogStreamProxy::operator<<(std::ostream &(*manip)(std::ostream &))
|
||||||
|
{
|
||||||
|
if (manip == static_cast<std::ostream &(*)(std::ostream &)>(std::endl))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Handle other manipulators if necessary (e.g., std::hex)
|
||||||
|
m_buffer << manip;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Pass the buffered content to the central Logger */
|
||||||
|
inline void ReLogStreamProxy::flush()
|
||||||
|
{
|
||||||
|
// Pass the buffered content to the central Logger
|
||||||
|
ReLog::getInstance().log(ReLog::Log{ .level = m_level,
|
||||||
|
.timestamp = m_timestamp,
|
||||||
|
.message = m_buffer.str() });
|
||||||
|
|
||||||
|
// Reset stream content
|
||||||
|
m_buffer.str("");
|
||||||
|
m_buffer.clear();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdi_toolBox::logs
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
* User interface
|
||||||
|
*/
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogError()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Error);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogWarning()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Warning);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogInfo()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Info);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogDebug()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Debug);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogComm()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Comm);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogMessage()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline sdi_toolBox::logs::ReLogStreamProxy LogDev()
|
||||||
|
{
|
||||||
|
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Dev);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline void LogCode(const std::string &msg = "", const std::source_location &location = std::source_location::current())
|
||||||
|
{
|
||||||
|
LogDev() << msg << ((!msg.empty()) ? " - " : "") << "(" << location.file_name() << " l." << location.line() << ")" << std::endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
6
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/namespace.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace sdi_ToolBox
|
||||||
|
* @brief Root namespace for SD-Innovation ToolBox utilities.
|
||||||
|
*/
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
{{copyright}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{version}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
{{license}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/statline.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticText *createTitleCtrl(wxWindow *parentWindow, const wxString &title, const long style = 0)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxStaticText(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
title,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
style);
|
||||||
|
ctrl->SetFont(ctrl->GetFont().Italic());
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticText *createLabelCtrl(wxWindow *parentWindow, const wxString &title = wxEmptyString, const long style = 0)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxStaticText(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
title,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
style);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxTextCtrl *createTextCtrl(wxWindow *parentWindow, const wxString &value = wxEmptyString, const long style = 0)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxTextCtrl(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
value,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
style);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxButton *createButtonCtrl(wxWindow *parentWindow, const wxString &label, const long style = 0)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxButton(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
label,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
style);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxBitmapButton *createBitmapButtonCtrl(wxWindow *parentWindow, const wxBitmap &bmp, const long style = 0)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxBitmapButton(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
bmp,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
style);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticLine *createVLineCtrl(wxWindow *parentWindow)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxStaticLine(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
wxVERTICAL);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticLine *createHLineCtrl(wxWindow *parentWindow)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxStaticLine(parentWindow,
|
||||||
|
wxID_ANY,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
wxHORIZONTAL);
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
74
src/api/project.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dbus/api/api.h>
|
||||||
|
#include <dbus/dBus.h>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace api::project
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class OperationType : uint8_t
|
||||||
|
{
|
||||||
|
Create = 0,
|
||||||
|
Open,
|
||||||
|
Save,
|
||||||
|
SaveAs,
|
||||||
|
Close,
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct ProjectOperationEvent : dBus::api::DefaultData<dBus::makeID("project.file.operation")>
|
||||||
|
{
|
||||||
|
ProjectOperationEvent() = default; // Default constructor
|
||||||
|
virtual ~ProjectOperationEvent() = default; // Default destructor
|
||||||
|
ProjectOperationEvent(const ProjectOperationEvent &obj) = default; // Copy constructor
|
||||||
|
ProjectOperationEvent(ProjectOperationEvent &&obj) noexcept = default; // Move constructor
|
||||||
|
ProjectOperationEvent &operator=(const ProjectOperationEvent &obj) = default; // Copy assignment operator
|
||||||
|
ProjectOperationEvent &operator=(ProjectOperationEvent &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
OperationType operationType;
|
||||||
|
std::filesystem::path filePath;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
if (operationType == OperationType::Open || operationType == OperationType::SaveAs)
|
||||||
|
return std::format("ProjectOperationEvent: operation={}, filePath={}", getOperationString(), filePath.string());
|
||||||
|
else
|
||||||
|
return std::format("ProjectOperationEvent: operation={}", getOperationString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] std::string getOperationString() const
|
||||||
|
{
|
||||||
|
switch (operationType)
|
||||||
|
{
|
||||||
|
using enum OperationType;
|
||||||
|
case Create:
|
||||||
|
return "Create";
|
||||||
|
case Open:
|
||||||
|
return "Open";
|
||||||
|
case Save:
|
||||||
|
return "Save";
|
||||||
|
case SaveAs:
|
||||||
|
return "SaveAs";
|
||||||
|
case Close:
|
||||||
|
return "Close";
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid project operation");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline dBus::api::PostReturnStatus postProjectOperationRequest(dBus::Bus &bus, const OperationType &operationType, const std::filesystem::path &filePath = "")
|
||||||
|
{
|
||||||
|
auto eventData = ProjectOperationEvent();
|
||||||
|
eventData.operationType = std::move(operationType);
|
||||||
|
eventData.filePath = std::move(filePath);
|
||||||
|
|
||||||
|
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using ProjectOperationMessage_ptr = std::shared_ptr<dBus::RequestMessage<ProjectOperationEvent, void>>;
|
||||||
|
} // namespace api::project
|
||||||
198
src/api/requirement.h
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <dbus/api/api.h>
|
||||||
|
#include <dbus/dBus.h>
|
||||||
|
|
||||||
|
namespace api::requirement
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using TimePoint = std::chrono::system_clock::time_point;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class Status
|
||||||
|
{
|
||||||
|
Draft, // The requirement is in draft state and has not been finalized
|
||||||
|
InReview, // The requirement is currently being reviewed by stakeholders
|
||||||
|
Approved, // The requirement has been approved and is ready for implementation
|
||||||
|
Rejected // The requirement has been rejected and will not be implemented
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct Metadata
|
||||||
|
{
|
||||||
|
std::string uuid; // Unique identifier for the requirement, generated automatically
|
||||||
|
std::string id; // Unique identifier for the requirement, provided by the user (e.g., "REQ-001")
|
||||||
|
std::string author; // Name of the person who created the requirement
|
||||||
|
TimePoint created_at; // Creation timestamp
|
||||||
|
TimePoint updated_at; // Last update timestamp
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct Details
|
||||||
|
{
|
||||||
|
std::string title; // Short name or title of the requirement
|
||||||
|
std::string description; // Detailed description of the requirement
|
||||||
|
std::string acceptance_criteria; // Criteria that must be met for the requirement to be considered complete
|
||||||
|
bool is_smart; // SMART indicator (Specific, Measurable, Achievable, Relevant, Time-bound)
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct Classification
|
||||||
|
{
|
||||||
|
std::string type; // Type of the requirement (e.g., "Functional", "Non-Functional", "Performance", etc.)
|
||||||
|
std::string category; // Category of the requirement (e.g., "UI", "Backend", "Security", etc.)
|
||||||
|
int priority; // Priority level of the requirement (e.g., 1 for highest priority, 5 for lowest)
|
||||||
|
Status status; // Current status of the requirement (e.g., "Draft", "In Review", "Approved", "Rejected", etc.)
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct Requirement
|
||||||
|
{
|
||||||
|
Metadata metadata; // General information about the requirement
|
||||||
|
Details details; // Detailed information about the requirement
|
||||||
|
Classification classification; // Classification information about the requirement
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct GetRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.get")>
|
||||||
|
{
|
||||||
|
virtual ~GetRequirementEvent() = default; // Default destructor
|
||||||
|
|
||||||
|
std::string uuid; // UUID of the requirement (empty for root-level requirements)
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
return std::format("GetRequirementEvent: uuid={}", uuid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline std::expected<Requirement, dBus::ReturnStatus> postGetRequirementRequest(dBus::Bus &bus, const std::string &uuid = "")
|
||||||
|
{
|
||||||
|
auto eventData = GetRequirementEvent();
|
||||||
|
eventData.uuid = std::move(uuid);
|
||||||
|
return dBus::api::requestData<decltype(eventData), Requirement>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using GetRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<GetRequirementEvent, Requirement>>;
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct GetChildrenRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.getChildren")>
|
||||||
|
{
|
||||||
|
virtual ~GetChildrenRequirementEvent() = default; // Default destructor
|
||||||
|
|
||||||
|
std::string uuid; // UUID of the requirement (empty for root-level requirements)
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
return std::format("GetChildrenRequirementEvent: uuid={}", uuid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline std::expected<std::vector<Requirement>, dBus::ReturnStatus> postGetChildrenRequirementRequest(dBus::Bus &bus, const std::string &uuid)
|
||||||
|
{
|
||||||
|
auto eventData = GetChildrenRequirementEvent();
|
||||||
|
eventData.uuid = std::move(uuid);
|
||||||
|
return dBus::api::requestData<decltype(eventData), std::vector<Requirement>>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using GetChildrenRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<GetChildrenRequirementEvent, std::vector<Requirement>>>;
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct AddRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.add")>
|
||||||
|
{
|
||||||
|
virtual ~AddRequirementEvent() = default; // Default destructor
|
||||||
|
|
||||||
|
std::string parentUuid; // UUID of the parent requirement (empty for root-level requirements)
|
||||||
|
Requirement requirementData; // Data of the requirement to be added
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
return std::format("AddRequirementEvent: parentUuid={}, id={}, title={}",
|
||||||
|
parentUuid,
|
||||||
|
requirementData.metadata.id,
|
||||||
|
requirementData.details.title);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline dBus::api::PostReturnStatus postAddRequirementRequest(dBus::Bus &bus, const std::string &parentUuid, const Requirement &requirement)
|
||||||
|
{
|
||||||
|
auto eventData = AddRequirementEvent();
|
||||||
|
eventData.parentUuid = std::move(parentUuid);
|
||||||
|
eventData.requirementData = std::move(requirement);
|
||||||
|
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using AddRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<AddRequirementEvent, void>>;
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct UpdateRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.update")>
|
||||||
|
{
|
||||||
|
virtual ~UpdateRequirementEvent() = default; // Default destructor
|
||||||
|
|
||||||
|
Requirement requirementData; // Data of the requirement to be updated
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
return std::format("UpdateRequirementEvent: id={}, title={}",
|
||||||
|
requirementData.metadata.id,
|
||||||
|
requirementData.details.title);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline dBus::api::PostReturnStatus postUpdateRequirementRequest(dBus::Bus &bus, const Requirement &requirement)
|
||||||
|
{
|
||||||
|
auto eventData = UpdateRequirementEvent();
|
||||||
|
eventData.requirementData = std::move(requirement);
|
||||||
|
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using UpdateRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<UpdateRequirementEvent, void>>;
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct DeleteRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.delete")>
|
||||||
|
{
|
||||||
|
virtual ~DeleteRequirementEvent() = default; // Default destructor
|
||||||
|
|
||||||
|
Requirement requirementData; // Data of the requirement to be deleted
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
return std::format("DeleteRequirementEvent: id={}, title={}",
|
||||||
|
requirementData.metadata.id,
|
||||||
|
requirementData.details.title);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline dBus::api::PostReturnStatus postDeleteRequirementRequest(dBus::Bus &bus, const Requirement &requirement)
|
||||||
|
{
|
||||||
|
auto eventData = DeleteRequirementEvent();
|
||||||
|
eventData.requirementData = std::move(requirement);
|
||||||
|
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using DeleteRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<DeleteRequirementEvent, void>>;
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
/* Post a requirement event to the bus. This function will post a
|
||||||
|
* RequirementEvent containing the provided requirement data to the
|
||||||
|
* bus, and return a PostReturnStatus indicating whether the post was
|
||||||
|
* successful or if there were any issues (e.g., no subscribers,
|
||||||
|
* timeout, etc.). */
|
||||||
|
// inline dBus::api::PostReturnStatus postRequirementRequest(dBus::Bus &bus,
|
||||||
|
// Requirement requirement)
|
||||||
|
//{
|
||||||
|
// auto eventData = RequirementEvent();
|
||||||
|
// eventData.requirementData = std::move(requirement);
|
||||||
|
//
|
||||||
|
// return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
|
||||||
|
// }
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace api::requirement
|
||||||
45
src/core/coreManager.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "coreManager.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace core;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
CoreManager::CoreManager()
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
CoreManager::~CoreManager()
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get a reference to the application bus */
|
||||||
|
dBus::Bus &CoreManager::getBus() const
|
||||||
|
{
|
||||||
|
return *m_bus;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Initialize the core manager (e.g., set up the bus, etc.) */
|
||||||
|
void CoreManager::init()
|
||||||
|
{
|
||||||
|
// Initialize the application bus
|
||||||
|
m_bus = make_unique<dBus::Bus>();
|
||||||
|
|
||||||
|
// Initialize other subsystems (e.g., logger, etc.)
|
||||||
|
m_logger = make_unique<logger::Logger>(*m_bus);
|
||||||
|
m_projectManager = make_unique<project::ProjectManager>(*m_bus);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Release resources used by the core manager (e.g., clean up the bus, etc.) */
|
||||||
|
void CoreManager::release()
|
||||||
|
{
|
||||||
|
// Clean up other subsystems (e.g., logger, etc.)
|
||||||
|
m_projectManager.reset();
|
||||||
|
m_logger.reset();
|
||||||
|
|
||||||
|
// Clean up the application bus
|
||||||
|
m_bus.reset();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
35
src/core/coreManager.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "logger/logger.h"
|
||||||
|
#include "projectManager/projectManager.h"
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace core
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class CoreManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CoreManager(); // Default constructor
|
||||||
|
virtual ~CoreManager(); // Default destructor
|
||||||
|
CoreManager(const CoreManager &obj) = delete; // Copy constructor
|
||||||
|
CoreManager(CoreManager &&obj) noexcept = delete; // Move constructor
|
||||||
|
CoreManager &operator=(const CoreManager &obj) = delete; // Copy assignment operator
|
||||||
|
CoreManager &operator=(CoreManager &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
[[nodiscard]] dBus::Bus &getBus() const; // Get a reference to the application bus
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<dBus::Bus> m_bus; // Application data bus
|
||||||
|
|
||||||
|
std::unique_ptr<logger::Logger> m_logger; // Application logger system
|
||||||
|
std::unique_ptr<project::ProjectManager> m_projectManager; // Requirement manager system
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init(); // Initialize the core manager (e.g., set up the bus, etc.)
|
||||||
|
void release(); // Release resources used by the core manager (e.g., clean up the bus, etc.)
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace core
|
||||||
172
src/core/logger/logger.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <syncstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace core::logger;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
Logger::Logger(dBus::Bus &bus)
|
||||||
|
: Node(bus)
|
||||||
|
{
|
||||||
|
// Initialize the log printing loop
|
||||||
|
runPrintingLoop();
|
||||||
|
|
||||||
|
// Initialize the bus listener thread
|
||||||
|
runBusListener();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
Logger::~Logger()
|
||||||
|
{
|
||||||
|
// Stop the bus listener thread if it's running
|
||||||
|
stopBusListener();
|
||||||
|
|
||||||
|
// Stop the log printing loop if it's running
|
||||||
|
stopPrintingLoop();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Run the loop that periodically prints log entries to the console */
|
||||||
|
void Logger::runPrintingLoop()
|
||||||
|
{
|
||||||
|
/* Start the printing thread to periodically print log entries to the console.
|
||||||
|
* The thread will continuously wait for messages and process them until a stop is requested */
|
||||||
|
// clang-format off
|
||||||
|
m_printingThread = std::jthread([this](const std::stop_token &stopToken)
|
||||||
|
{
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
// Sleep for a short duration to avoid busy waiting and reduce CPU usage
|
||||||
|
this_thread::sleep_for(100ms);
|
||||||
|
|
||||||
|
// Print the log entries after processing the message
|
||||||
|
if (m_logEntries.timer.isElapsed(LOG_PRINT_INTERVAL, true))
|
||||||
|
printLogEntries();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the log printing loop */
|
||||||
|
void Logger::stopPrintingLoop()
|
||||||
|
{
|
||||||
|
// Stop printing thread loop
|
||||||
|
m_printingThread.request_stop();
|
||||||
|
|
||||||
|
// Despite a jthread will automatically join in its destructor, we
|
||||||
|
// can explicitly join here to ensure that the thread has finished
|
||||||
|
// executing before the Logger object is destroyed.
|
||||||
|
// This is especially important if the thread is still running and
|
||||||
|
// may access resources that are being cleaned up in the destructor.
|
||||||
|
// By joining the thread, we can ensure a clean shutdown of the Logger
|
||||||
|
// and avoid potential issues with dangling threads or accessing invalid resources.
|
||||||
|
if (m_printingThread.joinable())
|
||||||
|
m_printingThread.join();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Print the log entries to the console */
|
||||||
|
void Logger::printLogEntries()
|
||||||
|
{
|
||||||
|
// Move the log entries to a local variable and clear the
|
||||||
|
// original vector to minimize the time the mutex is locked
|
||||||
|
std::vector<std::string> logEntries;
|
||||||
|
{
|
||||||
|
scoped_lock lock(m_logEntries.mtx);
|
||||||
|
logEntries = std::move(m_logEntries.entries);
|
||||||
|
m_logEntries.entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &entry : logEntries)
|
||||||
|
{
|
||||||
|
// Use osyncstream to ensure thread-safe output to the console
|
||||||
|
std::osyncstream syncOut(std::cout);
|
||||||
|
syncOut << entry << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a log message received from the bus */
|
||||||
|
void Logger::processUserMessage(const LogMessage_ptr &logMessage)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!logMessage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add the log entry to the vector of log entries
|
||||||
|
std::scoped_lock lock(m_logEntries.mtx);
|
||||||
|
m_logEntries.entries.push_back(logMessage->toString());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a system log message received from the bus */
|
||||||
|
void Logger::processSystemMessage(const std::string &logMessage)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (logMessage.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add the log entry to the vector of log entries
|
||||||
|
std::scoped_lock lock(m_logEntries.mtx);
|
||||||
|
m_logEntries.entries.push_back(logMessage);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Run the bus listener thread */
|
||||||
|
void Logger::runBusListener()
|
||||||
|
{
|
||||||
|
// Subscribe to the bus in broadcast mode to receive all messages posted to the bus
|
||||||
|
m_bus.subscribeToBroadcast(this);
|
||||||
|
|
||||||
|
/* Start the bus listener thread to process incoming messages related to requirements.
|
||||||
|
* The thread will continuously wait for messages and process them until a stop is requested */
|
||||||
|
// clang-format off
|
||||||
|
m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
|
||||||
|
{
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
// Wait for a message to be received
|
||||||
|
syncWaitForMessage();
|
||||||
|
|
||||||
|
// Process all received messages
|
||||||
|
while (getMessageCount() > 0)
|
||||||
|
{
|
||||||
|
auto message = popNextMessage();
|
||||||
|
if (message)
|
||||||
|
processMessageBus(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the bus listener thread */
|
||||||
|
void Logger::stopBusListener()
|
||||||
|
{
|
||||||
|
// Stop event thread loop
|
||||||
|
m_busListenerThread.request_stop();
|
||||||
|
|
||||||
|
// Wake up event thread if waiting
|
||||||
|
notifyMessageQueue();
|
||||||
|
|
||||||
|
// Join the bus listener thread to ensure it has finished
|
||||||
|
// executing before the Logger object is destroyed
|
||||||
|
if (m_busListenerThread.joinable())
|
||||||
|
m_busListenerThread.join();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a message received from the bus */
|
||||||
|
void Logger::processMessageBus(const Message_ptr &message)
|
||||||
|
{
|
||||||
|
// Check the message type and process accordingly
|
||||||
|
const auto messageType = message->getMessageTypeID();
|
||||||
|
|
||||||
|
if (messageType == dBus::makeID("log.message"))
|
||||||
|
{
|
||||||
|
// Process log message
|
||||||
|
processUserMessage(message->as<dBus::EventMessage<api::log::LogMessage>>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Process system log message (for any other message type, we will log it as a system message)
|
||||||
|
processSystemMessage(message->toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
53
src/core/logger/logger.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dBus/api/log.h>
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sdi_toolBox/dateTime/timer.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace core::logger
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Logger : public dBus::Node
|
||||||
|
{
|
||||||
|
static constexpr auto LOG_PRINT_INTERVAL = std::chrono::milliseconds{ 500 };
|
||||||
|
|
||||||
|
public:
|
||||||
|
Logger() = delete; // Default constructor
|
||||||
|
virtual ~Logger(); // Default destructor
|
||||||
|
Logger(const Logger &obj) = delete; // Copy constructor
|
||||||
|
Logger(Logger &&obj) noexcept = delete; // Move constructor
|
||||||
|
Logger &operator=(const Logger &obj) = delete; // Copy assignment operator
|
||||||
|
Logger &operator=(Logger &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Logger(dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
std::mutex mtx; // Mutex to protect access to the log entries vector
|
||||||
|
sdi_toolBox::dateTime::Timer timer; // Timer to control the frequency of log printing
|
||||||
|
std::vector<std::string> entries; // Vector to store log entries received from the bus
|
||||||
|
} m_logEntries;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void runPrintingLoop(); // Run the loop that periodically prints log entries to the console
|
||||||
|
void stopPrintingLoop(); // Stop the log printing loop
|
||||||
|
void printLogEntries(); // Print the log entries to the console
|
||||||
|
std::jthread m_printingThread; // Thread for printing log entries to the console
|
||||||
|
|
||||||
|
// Logger management
|
||||||
|
using LogMessage_ptr = std::shared_ptr<dBus::EventMessage<api::log::LogMessage>>;
|
||||||
|
void processUserMessage(const LogMessage_ptr &logMessage); // Process a log message received from the bus
|
||||||
|
void processSystemMessage(const std::string &logMessage); // Process a system log message received from the bus
|
||||||
|
|
||||||
|
// dBus management
|
||||||
|
using Message_ptr = std::shared_ptr<dBus::Message>;
|
||||||
|
void runBusListener(); // Run the bus listener thread
|
||||||
|
void stopBusListener(); // Stop the bus listener thread
|
||||||
|
void processMessageBus(const Message_ptr &message); // Process a message received from the bus
|
||||||
|
std::jthread m_busListenerThread; // Thread for listening to bus messages related to requirements
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace core::logger
|
||||||
78
src/core/projectManager/RequirementManager.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "requirementManager.h"
|
||||||
|
|
||||||
|
#include "requirementItem.h"
|
||||||
|
|
||||||
|
#include <sdi_toolBox/generic/uuid.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace core::project;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
RequirementManager::RequirementManager()
|
||||||
|
{
|
||||||
|
m_rootRequirement = make_shared<RequirementItem>(*this); // Create the root requirement item and set it as the root requirement
|
||||||
|
m_requirementsByUuid[m_rootRequirement->metadata.uuid] = m_rootRequirement; // Register the root requirement item
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the root requirement */
|
||||||
|
RequirementManager::RequirementItemPtr RequirementManager::getRootRequirement() const
|
||||||
|
{
|
||||||
|
return m_rootRequirement; // Return the root requirement item pointer
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the vector of child requirement items of the root requirement */
|
||||||
|
std::vector<RequirementManager::RequirementItemPtr> RequirementManager::getRootRequirementChild() const
|
||||||
|
{
|
||||||
|
return m_rootRequirement->getChildren(); // Return the vector of child requirement items of the root requirement
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get a requirement by its UUID */
|
||||||
|
RequirementManager::RequirementItemPtr RequirementManager::getRequirement(const std::string &uuid) const
|
||||||
|
{
|
||||||
|
if (uuid.empty())
|
||||||
|
return m_rootRequirement; // Return the root requirement item pointer if the UUID is empty
|
||||||
|
|
||||||
|
if (m_requirementsByUuid.contains(uuid))
|
||||||
|
return m_requirementsByUuid.at(uuid); // Return the requirement item pointer if found in the map
|
||||||
|
|
||||||
|
return {}; // Return nullptr if the requirement with the given UUID is not found
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the vector of child requirement items of a requirement by its UUID */
|
||||||
|
std::vector<RequirementManager::RequirementItemPtr> RequirementManager::getRequirementChildren(const std::string &uuid) const
|
||||||
|
{
|
||||||
|
if (m_requirementsByUuid.contains(uuid))
|
||||||
|
return m_requirementsByUuid.at(uuid)->getChildren(); // Return the vector of child requirement items if the requirement with the given UUID is found in the map
|
||||||
|
return {}; // Return an empty vector if the requirement with the given UUID is not found
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Generate a unique UUID string that is not already registered in the manager's internal map */
|
||||||
|
std::string RequirementManager::generateUniqueUuid() const
|
||||||
|
{
|
||||||
|
std::string uuid;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
uuid = sdi_toolBox::generic::UuidGenerator::uuid_v4(); // Generate a random UUID v4 string
|
||||||
|
} while (isRequirementRegistered(uuid)); // Check if the generated UUID is already registered
|
||||||
|
|
||||||
|
return uuid; // Return the unique UUID string
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if a requirement with the given UUID is registered in the manager's internal map */
|
||||||
|
bool RequirementManager::isRequirementRegistered(const std::string &uuid) const
|
||||||
|
{
|
||||||
|
return m_requirementsByUuid.contains(uuid);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Register a requirement in the manager's internal map for quick lookup by UUID */
|
||||||
|
void RequirementManager::registerRequirement(const RequirementItemPtr &requirement)
|
||||||
|
{
|
||||||
|
m_requirementsByUuid[requirement->metadata.uuid] = requirement;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unregister a requirement from the manager's internal map */
|
||||||
|
void RequirementManager::unregisterRequirement(const std::string &uuid)
|
||||||
|
{
|
||||||
|
m_requirementsByUuid.erase(uuid);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
40
src/core/projectManager/RequirementManager.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace core::project
|
||||||
|
{
|
||||||
|
class RequirementItem;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementManager
|
||||||
|
{
|
||||||
|
using RequirementItemPtr = std::shared_ptr<RequirementItem>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RequirementManager(); // Default constructor
|
||||||
|
virtual ~RequirementManager() = default; // Default destructor
|
||||||
|
RequirementManager(const RequirementManager &obj) = delete; // Copy constructor
|
||||||
|
RequirementManager(RequirementManager &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementManager &operator=(const RequirementManager &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementManager &operator=(RequirementManager &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
[[nodiscard]] RequirementItemPtr getRootRequirement() const; // Get the root requirement
|
||||||
|
[[nodiscard]] std::vector<RequirementItemPtr> getRootRequirementChild() const; // Get the vector of child requirement items of the root requirement
|
||||||
|
|
||||||
|
[[nodiscard]] RequirementItemPtr getRequirement(const std::string &uuid) const; // Get a requirement by its UUID
|
||||||
|
[[nodiscard]] std::vector<RequirementItemPtr> getRequirementChildren(const std::string &uuid) const; // Get the vector of child requirement items of a requirement by its UUID
|
||||||
|
|
||||||
|
// Requirement management functions
|
||||||
|
[[nodiscard]] std::string generateUniqueUuid() const; // Generate a unique UUID string that is not already registered in the manager's internal map
|
||||||
|
[[nodiscard]] bool isRequirementRegistered(const std::string &uuid) const; // Check if a requirement with the given UUID is registered in the manager's internal map
|
||||||
|
void registerRequirement(const RequirementItemPtr &requirement); // Register a requirement in the manager's internal map for quick lookup by UUID
|
||||||
|
void unregisterRequirement(const std::string &uuid); // Unregister a requirement from the manager's internal map
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RequirementItemPtr m_rootRequirement; // Root requirement of the current project
|
||||||
|
std::unordered_map<std::string, RequirementItemPtr> m_requirementsByUuid; // Map of requirement UUID to requirement pointer for quick lookup
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace core::project
|
||||||
367
src/core/projectManager/projectManager.cpp
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
#include "projectManager.h"
|
||||||
|
|
||||||
|
#include "requirementItem.h"
|
||||||
|
#include "requirementManager.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace core::project;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
ProjectManager::ProjectManager(dBus::Bus &bus)
|
||||||
|
: Node(bus)
|
||||||
|
{
|
||||||
|
// Initialize the bus listener thread
|
||||||
|
runBusListener();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
ProjectManager::~ProjectManager()
|
||||||
|
{
|
||||||
|
// Stop the bus listener thread if it's running
|
||||||
|
stopBusListener();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Open a project file and load the requirements */
|
||||||
|
void ProjectManager::openFile(const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Save the current requirements to a project file */
|
||||||
|
void ProjectManager::saveFile(const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Run the bus listener thread */
|
||||||
|
void ProjectManager::runBusListener()
|
||||||
|
{
|
||||||
|
// Subscribe to the bus for messages related to project file and requirement operations
|
||||||
|
subscribe(dBus::makeID("project.file.operation"));
|
||||||
|
subscribe(dBus::makeID("requirement.get"));
|
||||||
|
subscribe(dBus::makeID("requirement.requirement.getChildren"));
|
||||||
|
subscribe(dBus::makeID("requirement.add"));
|
||||||
|
subscribe(dBus::makeID("requirement.update"));
|
||||||
|
subscribe(dBus::makeID("requirement.delete"));
|
||||||
|
|
||||||
|
/* Start the bus listener thread to process incoming messages related to requirements.
|
||||||
|
* The thread will continuously wait for messages and process them until a stop is requested. */
|
||||||
|
// clang-format off
|
||||||
|
m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
|
||||||
|
{
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
// Wait for a message to be received
|
||||||
|
syncWaitForMessage();
|
||||||
|
|
||||||
|
// Process all received messages
|
||||||
|
while (getMessageCount() > 0)
|
||||||
|
{
|
||||||
|
auto message = popNextMessage();
|
||||||
|
if (message)
|
||||||
|
processMessageBus(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the bus listener thread */
|
||||||
|
void ProjectManager::stopBusListener()
|
||||||
|
{
|
||||||
|
// Stop event thread loop
|
||||||
|
m_busListenerThread.request_stop();
|
||||||
|
|
||||||
|
// Wake up event thread if waiting
|
||||||
|
notifyMessageQueue();
|
||||||
|
|
||||||
|
/* No need to join the thread explicitly as std::jthread will
|
||||||
|
* automatically join in its destructor.
|
||||||
|
* Subscribe to the bus will be automatically cleaned up in the
|
||||||
|
* Node destructor. */
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a message received from the bus */
|
||||||
|
void ProjectManager::processMessageBus(const Message_ptr &message)
|
||||||
|
{
|
||||||
|
const auto messageType = message->getMessageTypeID();
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
// Process project events
|
||||||
|
case dBus::makeID("project.file.operation"):
|
||||||
|
{
|
||||||
|
on_projectOperationEvent(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process requirement events
|
||||||
|
case dBus::makeID("requirement.get"):
|
||||||
|
{
|
||||||
|
on_requirementGetEvent(message->as<dBus::RequestMessage<api::requirement::GetRequirementEvent, api::requirement::Requirement>>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case dBus::makeID("requirement.getChildren"):
|
||||||
|
{
|
||||||
|
on_requirementGetChildrenEvent(message->as<dBus::RequestMessage<api::requirement::GetChildrenRequirementEvent, std::vector<api::requirement::Requirement>>>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case dBus::makeID("requirement.add"):
|
||||||
|
{
|
||||||
|
on_requirementAddEvent(message->as<dBus::RequestMessage<api::requirement::AddRequirementEvent, void>>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case dBus::makeID("requirement.update"):
|
||||||
|
{
|
||||||
|
on_requirementUpdateEvent(message->as<dBus::RequestMessage<api::requirement::UpdateRequirementEvent, void>>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case dBus::makeID("requirement.delete"):
|
||||||
|
{
|
||||||
|
on_requirementDeleteEvent(message->as<dBus::RequestMessage<api::requirement::DeleteRequirementEvent, void>>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a file operation event */
|
||||||
|
void ProjectManager::on_projectOperationEvent(const Message_ptr &message)
|
||||||
|
{
|
||||||
|
const auto specializedMessage = message->as<dBus::RequestMessage<api::project::ProjectOperationEvent, void>>();
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (!specializedMessage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (specializedMessage->value.operationType)
|
||||||
|
{
|
||||||
|
using enum api::project::OperationType;
|
||||||
|
case Create:
|
||||||
|
on_projectCreateFileEvent(specializedMessage);
|
||||||
|
break;
|
||||||
|
case Open:
|
||||||
|
on_projectOpenFileEvent(specializedMessage);
|
||||||
|
break;
|
||||||
|
case Save:
|
||||||
|
case SaveAs:
|
||||||
|
on_projectSaveFileEvent(specializedMessage);
|
||||||
|
break;
|
||||||
|
case Close:
|
||||||
|
on_projectCloseFileEvent(specializedMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a file creation event */
|
||||||
|
void ProjectManager::on_projectCreateFileEvent(const api::project::ProjectOperationMessage_ptr &message)
|
||||||
|
{
|
||||||
|
m_requirementManager = make_shared<RequirementManager>();
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a file open event */
|
||||||
|
void ProjectManager::on_projectOpenFileEvent(const api::project::ProjectOperationMessage_ptr &message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
openFile(message->value.filePath);
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a file save event */
|
||||||
|
void ProjectManager::on_projectSaveFileEvent(const api::project::ProjectOperationMessage_ptr &message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (message->value.operationType == api::project::OperationType::SaveAs)
|
||||||
|
{
|
||||||
|
if (message->value.filePath.empty())
|
||||||
|
throw std::runtime_error("File path is empty for SaveAs operation");
|
||||||
|
saveFile(message->value.filePath);
|
||||||
|
}
|
||||||
|
else if (message->value.operationType == api::project::OperationType::Save)
|
||||||
|
{
|
||||||
|
if (m_filePath.empty())
|
||||||
|
throw std::runtime_error("File path is empty for Save operation");
|
||||||
|
saveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a file close event */
|
||||||
|
void ProjectManager::on_projectCloseFileEvent(const api::project::ProjectOperationMessage_ptr &message)
|
||||||
|
{
|
||||||
|
m_requirementManager = {};
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a requirement get event */
|
||||||
|
void ProjectManager::on_requirementGetEvent(const api::requirement::GetRequirementMessage_ptr &message) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_requirementManager)
|
||||||
|
throw std::runtime_error("No project file is currently opened");
|
||||||
|
|
||||||
|
// Get requirement handle
|
||||||
|
const auto requirement = m_requirementManager->getRequirement(message->value.uuid);
|
||||||
|
if (!requirement)
|
||||||
|
throw std::runtime_error("Requirement not found");
|
||||||
|
|
||||||
|
message->responsePromise.set_value(requirement->toRequirement());
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a requirement get children event */
|
||||||
|
void ProjectManager::on_requirementGetChildrenEvent(const api::requirement::GetChildrenRequirementMessage_ptr &message) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_requirementManager)
|
||||||
|
throw std::runtime_error("No project file is currently opened");
|
||||||
|
|
||||||
|
// Get requirement handle
|
||||||
|
const auto requirement = m_requirementManager->getRequirement(message->value.uuid);
|
||||||
|
if (!requirement)
|
||||||
|
throw std::runtime_error("Requirement not found");
|
||||||
|
|
||||||
|
// Get children requirements
|
||||||
|
const auto &children = requirement->getChildren();
|
||||||
|
vector<api::requirement::Requirement> childrenData;
|
||||||
|
childrenData.reserve(children.size());
|
||||||
|
for (const auto &child : children)
|
||||||
|
childrenData.push_back(child->toRequirement());
|
||||||
|
|
||||||
|
message->responsePromise.set_value(childrenData);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a requirement addition event */
|
||||||
|
void ProjectManager::on_requirementAddEvent(const api::requirement::AddRequirementMessage_ptr &message) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_requirementManager)
|
||||||
|
throw std::runtime_error("No project file is currently opened");
|
||||||
|
|
||||||
|
// Get parent requirement handle
|
||||||
|
const auto parentRequirement = message->value.parentUuid.empty()
|
||||||
|
? m_requirementManager->getRootRequirement()
|
||||||
|
: m_requirementManager->getRequirement(message->value.parentUuid);
|
||||||
|
if (!parentRequirement)
|
||||||
|
throw std::runtime_error("Parent requirement not found");
|
||||||
|
|
||||||
|
// Append the new requirement to the parent requirement
|
||||||
|
parentRequirement->appendChild(message->value.requirementData);
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a requirement update event */
|
||||||
|
void ProjectManager::on_requirementUpdateEvent(const api::requirement::UpdateRequirementMessage_ptr &message) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_requirementManager)
|
||||||
|
throw std::runtime_error("No project file is currently opened");
|
||||||
|
|
||||||
|
// Get requirement handle
|
||||||
|
const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
|
||||||
|
if (!requirement)
|
||||||
|
throw std::runtime_error("Requirement not found");
|
||||||
|
|
||||||
|
// Update the requirement data
|
||||||
|
requirement->update(message->value.requirementData);
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process a requirement deletion event */
|
||||||
|
void ProjectManager::on_requirementDeleteEvent(const api::requirement::DeleteRequirementMessage_ptr &message) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_requirementManager)
|
||||||
|
throw std::runtime_error("No project file is currently opened");
|
||||||
|
|
||||||
|
// Get requirement handle
|
||||||
|
const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
|
||||||
|
if (!requirement)
|
||||||
|
throw std::runtime_error("Requirement not found");
|
||||||
|
|
||||||
|
// Get parent requirement handle
|
||||||
|
const auto parentRequirement = requirement->getParent();
|
||||||
|
if (!parentRequirement)
|
||||||
|
{
|
||||||
|
// If the requirement to be deleted is the root requirement,
|
||||||
|
// we cannot delete it as it is the base of the requirements hierarchy
|
||||||
|
throw std::runtime_error("Cannot delete root requirement");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the requirement from its parent
|
||||||
|
parentRequirement->removeChild(requirement->metadata.uuid);
|
||||||
|
|
||||||
|
message->responsePromise.set_value();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
message->responsePromise.set_exception(std::make_exception_ptr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
56
src/core/projectManager/projectManager.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <api/project.h>
|
||||||
|
#include <api/requirement.h>
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace core::project
|
||||||
|
{
|
||||||
|
class RequirementManager;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ProjectManager : public dBus::Node
|
||||||
|
{
|
||||||
|
using RequirementManagerPtr = std::shared_ptr<RequirementManager>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ProjectManager() = delete; // Default constructor
|
||||||
|
virtual ~ProjectManager(); // Default destructor
|
||||||
|
ProjectManager(const ProjectManager &obj) = delete; // Copy constructor
|
||||||
|
ProjectManager(ProjectManager &&obj) noexcept = delete; // Move constructor
|
||||||
|
ProjectManager &operator=(const ProjectManager &obj) = delete; // Copy assignment operator
|
||||||
|
ProjectManager &operator=(ProjectManager &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit ProjectManager(dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::filesystem::path m_filePath; // Path of the currently opened project file
|
||||||
|
|
||||||
|
RequirementManagerPtr m_requirementManager; // Pointer to the requirement manager of the current project
|
||||||
|
|
||||||
|
private:
|
||||||
|
void openFile(const std::filesystem::path &filePath); // Open a project file and load the requirements
|
||||||
|
void saveFile(const std::filesystem::path &filePath = ""); // Save the current requirements to a project file
|
||||||
|
|
||||||
|
// dBus management
|
||||||
|
using Message_ptr = std::shared_ptr<dBus::Message>;
|
||||||
|
void runBusListener(); // Run the bus listener thread
|
||||||
|
void stopBusListener(); // Stop the bus listener thread
|
||||||
|
void processMessageBus(const Message_ptr &message); // Process a message received from the bus
|
||||||
|
std::jthread m_busListenerThread; // Thread for listening to bus messages related to requirements
|
||||||
|
|
||||||
|
// Events processing
|
||||||
|
void on_projectOperationEvent(const Message_ptr &message); // Process a file operation event
|
||||||
|
void on_projectCreateFileEvent(const api::project::ProjectOperationMessage_ptr &message); // Process a file creation event
|
||||||
|
void on_projectOpenFileEvent(const api::project::ProjectOperationMessage_ptr &message); // Process a file opening event
|
||||||
|
void on_projectSaveFileEvent(const api::project::ProjectOperationMessage_ptr &message); // Process a file saving event
|
||||||
|
void on_projectCloseFileEvent(const api::project::ProjectOperationMessage_ptr &message); // Process a file closing event
|
||||||
|
void on_requirementGetEvent(const api::requirement::GetRequirementMessage_ptr &message) const; // Process a requirement get event
|
||||||
|
void on_requirementGetChildrenEvent(const api::requirement::GetChildrenRequirementMessage_ptr &message) const; // Process a requirement get children event
|
||||||
|
void on_requirementAddEvent(const api::requirement::AddRequirementMessage_ptr &message) const; // Process a requirement addition event
|
||||||
|
void on_requirementUpdateEvent(const api::requirement::UpdateRequirementMessage_ptr &message) const; // Process a requirement update event
|
||||||
|
void on_requirementDeleteEvent(const api::requirement::DeleteRequirementMessage_ptr &message) const; // Process a requirement deletion event
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace core::project
|
||||||
94
src/core/projectManager/requirementItem.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include "requirementItem.h"
|
||||||
|
|
||||||
|
#include "requirementManager.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace core::project;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
RequirementItem::RequirementItem(RequirementManager &manager, const std::shared_ptr<RequirementItem> &parentRequirement)
|
||||||
|
: m_requirementManager(manager)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
metadata.uuid = m_requirementManager.generateUniqueUuid(); // Generate a unique UUID for this requirement item
|
||||||
|
m_parent = parentRequirement; // Set the m_parent requirement item
|
||||||
|
|
||||||
|
// Register this requirement item in the manager's internal map
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Convert this requirement item to an api::requirement::Requirement struct and return it */
|
||||||
|
api::requirement::Requirement RequirementItem::toRequirement() const
|
||||||
|
{
|
||||||
|
return { .metadata = metadata,
|
||||||
|
.details = details,
|
||||||
|
.classification = classification };
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update the data of this requirement item with the provided updated requirement data */
|
||||||
|
void RequirementItem::update(const api::requirement::Requirement &updatedData)
|
||||||
|
{
|
||||||
|
const auto currentUUID = metadata.uuid; // Store the current UUID before updating
|
||||||
|
|
||||||
|
// Update the requirement data with the provided updated requirement data
|
||||||
|
metadata = updatedData.metadata;
|
||||||
|
details = updatedData.details;
|
||||||
|
classification = updatedData.classification;
|
||||||
|
|
||||||
|
// Restore the original UUID to maintain consistency
|
||||||
|
metadata.uuid = currentUUID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the m_parent requirement item of this requirement item (nullptr for root) */
|
||||||
|
RequirementItem::RequirementItemPtr RequirementItem::getParent() const
|
||||||
|
{
|
||||||
|
// Return the m_parent requirement item pointer if it exists
|
||||||
|
// or nullptr if this requirement item is the root
|
||||||
|
return m_parent.lock();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Append a child requirement item to this requirement item */
|
||||||
|
void RequirementItem::appendChild(const Requirement &child)
|
||||||
|
{
|
||||||
|
// Create a new requirement item for the child requirement and set this requirement item as its m_parent
|
||||||
|
const auto childItem = make_shared<RequirementItem>(m_requirementManager, shared_from_this());
|
||||||
|
m_requirementManager.registerRequirement(childItem); // Register the child requirement item
|
||||||
|
|
||||||
|
childItem->update(child); // Update the child requirement item with the provided child requirement data
|
||||||
|
|
||||||
|
// Append the child requirement item to the vector of children
|
||||||
|
m_children.push_back(childItem);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Remove a child requirement item from this requirement item by its UUID */
|
||||||
|
void RequirementItem::removeChild(const std::string &childUuid)
|
||||||
|
{
|
||||||
|
// Unregister the child requirement item from the manager's internal map
|
||||||
|
m_requirementManager.unregisterRequirement(childUuid);
|
||||||
|
|
||||||
|
// Find the child requirement item with the given UUID
|
||||||
|
const auto childIt = ranges::find_if(m_children,
|
||||||
|
[&childUuid](const RequirementItemPtr &child)
|
||||||
|
{ return child->metadata.uuid == childUuid; });
|
||||||
|
if (childIt == m_children.end())
|
||||||
|
throw std::runtime_error("Child requirement item with UUID " + childUuid + " not found");
|
||||||
|
|
||||||
|
(*childIt)->removeChildren(); // Recursively remove all child requirement items of the child requirement item to be removed
|
||||||
|
m_children.erase(childIt); // Remove the child requirement item from the vector of children
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Remove all child requirement items from this requirement item */
|
||||||
|
void RequirementItem::removeChildren()
|
||||||
|
{
|
||||||
|
while (!m_children.empty())
|
||||||
|
{
|
||||||
|
const auto child = m_children.back(); // Get the last child requirement item
|
||||||
|
removeChild(child->metadata.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the vector of child requirement items of this requirement item */
|
||||||
|
std::vector<RequirementItem::RequirementItemPtr> RequirementItem::getChildren() const
|
||||||
|
{
|
||||||
|
return m_children;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
44
src/core/projectManager/requirementItem.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <api/requirement.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace core::project
|
||||||
|
{
|
||||||
|
class RequirementManager;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementItem : public std::enable_shared_from_this<RequirementItem>
|
||||||
|
, public api::requirement::Requirement
|
||||||
|
{
|
||||||
|
using RequirementItemPtr = std::shared_ptr<RequirementItem>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RequirementItem() = delete; // Default constructor
|
||||||
|
virtual ~RequirementItem() = default; // Default destructor
|
||||||
|
RequirementItem(const RequirementItem &obj) = delete; // Copy constructor
|
||||||
|
RequirementItem(RequirementItem &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementItem &operator=(const RequirementItem &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementItem &operator=(RequirementItem &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequirementItem(RequirementManager &manager, // Constructor
|
||||||
|
const std::shared_ptr<RequirementItem> &parentRequirement = nullptr);
|
||||||
|
|
||||||
|
// Management functions
|
||||||
|
[[nodiscard]] Requirement toRequirement() const; // Convert this requirement item to an api::requirement::Requirement struct and return it
|
||||||
|
void update(const Requirement &updatedData); // Update the data of this requirement item with the provided updated requirement data
|
||||||
|
|
||||||
|
// Children management functions
|
||||||
|
RequirementItemPtr getParent() const; // Get the m_parent requirement item of this requirement item
|
||||||
|
void appendChild(const Requirement &child); // Append a child requirement item to this requirement item
|
||||||
|
void removeChild(const std::string &childUuid); // Remove a child requirement item from this requirement item by its UUID
|
||||||
|
void removeChildren(); // Remove all child requirement items from this requirement item
|
||||||
|
[[nodiscard]] std::vector<RequirementItemPtr> getChildren() const; // Get the vector of child requirement items of this requirement item
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RequirementManager &m_requirementManager; // Reference to the requirement manager that owns this requirement item
|
||||||
|
std::weak_ptr<RequirementItem> m_parent; // Weak pointer to the m_parent requirement item (nullptr for root)
|
||||||
|
std::vector<RequirementItemPtr> m_children; // Vector of shared pointers to child requirement items
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace core::project
|
||||||
220
src/dBus/api/api.h
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "logWrapper.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <dBus/defs.h>
|
||||||
|
#include <expected>
|
||||||
|
#include <format>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dBus::api
|
||||||
|
{
|
||||||
|
// Define a type alias for the return status of post
|
||||||
|
// operations, using std::expected to represent success
|
||||||
|
// or failure with an error code
|
||||||
|
using PostReturnStatus = std::expected<void, dBus::ReturnStatus>;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct BaseDefaultData
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template<HashID ID>
|
||||||
|
struct DefaultData : BaseDefaultData
|
||||||
|
{
|
||||||
|
DefaultData() = default; // Default constructor
|
||||||
|
virtual ~DefaultData() = default; // Default destructor
|
||||||
|
DefaultData(const DefaultData &obj) = default; // Copy constructor
|
||||||
|
DefaultData(DefaultData &&obj) noexcept = default; // Move constructor
|
||||||
|
DefaultData &operator=(const DefaultData &obj) = default; // Copy assignment operator
|
||||||
|
DefaultData &operator=(DefaultData &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
|
constexpr HashID getID() const
|
||||||
|
{
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] virtual std::string toString() const = 0; // Convert the data to a string representation for logging or debugging
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Generic message API template for simple data transfer (fire-and-forget)
|
||||||
|
template<class T>
|
||||||
|
class MessageApi : public EventMessage<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageApi() = delete; // Default constructor
|
||||||
|
virtual ~MessageApi() = default; // Default destructor
|
||||||
|
MessageApi(const MessageApi &obj) = delete; // Copy constructor
|
||||||
|
MessageApi(MessageApi &&obj) noexcept = delete; // Move constructor
|
||||||
|
MessageApi &operator=(const MessageApi &obj) = delete; // Copy assignment operator
|
||||||
|
MessageApi &operator=(MessageApi &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit MessageApi(const HashID &messageTypeID, T data, const MessageCategory &category = MessageCategory::System) // Constructor
|
||||||
|
: EventMessage<T>(messageTypeID, category, std::move(data))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the message data to a string for logging or debugging
|
||||||
|
[[nodiscard]] std::string serializeData() const override
|
||||||
|
{
|
||||||
|
if constexpr (std::is_base_of_v<BaseDefaultData, T>)
|
||||||
|
{
|
||||||
|
return this->value.toString();
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_arithmetic_v<T>)
|
||||||
|
{
|
||||||
|
return std::to_string(this->value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Request message API template for data transfer with response
|
||||||
|
template<class TRequest, class TResponse>
|
||||||
|
class RequestMessageApi : public RequestMessage<TRequest, TResponse>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequestMessageApi() = delete; // Default constructor
|
||||||
|
virtual ~RequestMessageApi() = default; // Default destructor
|
||||||
|
RequestMessageApi(const RequestMessageApi &obj) = delete; // Copy constructor
|
||||||
|
RequestMessageApi(RequestMessageApi &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequestMessageApi &operator=(const RequestMessageApi &obj) = delete; // Copy assignment operator
|
||||||
|
RequestMessageApi &operator=(RequestMessageApi &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequestMessageApi(const HashID &messageTypeID, TRequest request, const MessageCategory &category = MessageCategory::System)
|
||||||
|
: RequestMessage<TRequest, TResponse>(messageTypeID, category, std::move(request), createResponse())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the message data to a string for logging or debugging
|
||||||
|
[[nodiscard]] std::string serializeData() const override
|
||||||
|
{
|
||||||
|
if constexpr (std::is_base_of_v<BaseDefaultData, TRequest>)
|
||||||
|
{
|
||||||
|
return this->value.toString();
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_arithmetic_v<TRequest>)
|
||||||
|
{
|
||||||
|
return std::to_string(this->value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto createResponse()
|
||||||
|
{
|
||||||
|
if constexpr (std::is_void_v<TResponse>)
|
||||||
|
return nullptr; // Use nullptr for void response type since there is no value to return
|
||||||
|
else
|
||||||
|
return std::make_shared<TResponse>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Post a simple data message (fire-and-forget)
|
||||||
|
template<class T>
|
||||||
|
bool postData(Bus &bus,
|
||||||
|
const HashID &messageTypeID,
|
||||||
|
T data,
|
||||||
|
const MessageCategory &category)
|
||||||
|
{
|
||||||
|
auto msg = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
|
||||||
|
return bus.post(msg);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Post a data message with subscriber check
|
||||||
|
template<class T>
|
||||||
|
std::expected<void, ReturnStatus> postDataChecked(Bus &bus,
|
||||||
|
const HashID &messageTypeID,
|
||||||
|
T data,
|
||||||
|
const MessageCategory &category)
|
||||||
|
{
|
||||||
|
auto msg = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
|
||||||
|
const auto subscribersFound = bus.post(msg);
|
||||||
|
|
||||||
|
if (!subscribersFound)
|
||||||
|
{
|
||||||
|
return std::unexpected(ReturnStatus::NoSubscribers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class TRequest, class TResponse>
|
||||||
|
std::expected<TResponse, ReturnStatus> requestData(Bus &bus,
|
||||||
|
const HashID &messageTypeID,
|
||||||
|
TRequest request,
|
||||||
|
const MessageCategory &category,
|
||||||
|
const std::chrono::milliseconds timeout = DEFAULT_TIMEOUT)
|
||||||
|
{
|
||||||
|
auto msg = std::make_shared<RequestMessageApi<TRequest, TResponse>>(messageTypeID, std::move(request), category);
|
||||||
|
auto future = msg->getFuture();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Post the message to the bus and check if there are subscribers
|
||||||
|
const auto subscribersFound = bus.post(msg);
|
||||||
|
if (!subscribersFound)
|
||||||
|
{
|
||||||
|
::api::log::postLogWarning(bus, std::format("No subscribers found for message type: 0x{:08x}", msg->getMessageTypeID()));
|
||||||
|
return std::unexpected(ReturnStatus::NoSubscribers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto status = future.wait_for(timeout);
|
||||||
|
if (status == std::future_status::ready)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_void_v<TResponse>)
|
||||||
|
{
|
||||||
|
// Wait for the future to be ready and check for exceptions,
|
||||||
|
// but ignore the value since it's void
|
||||||
|
future.get();
|
||||||
|
|
||||||
|
return {}; // Return success without a value
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Wait for the future to be ready and return the value,
|
||||||
|
// or propagate any exceptions that occurred during the
|
||||||
|
// request handling
|
||||||
|
return future.get(); // Return success with value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status == std::future_status::timeout)
|
||||||
|
{
|
||||||
|
::api::log::postLogWarning(bus, std::format("Timeout while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
|
||||||
|
return std::unexpected(ReturnStatus::Timeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
::api::log::postLogError(bus, std::format("Unexpected future status while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
|
||||||
|
return std::unexpected(ReturnStatus::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
::api::log::postLogError(bus, std::format("Exception in requestData for message type: 0x{:08x}: {}", msg->getMessageTypeID(), e.what()));
|
||||||
|
// return std::unexpected(ReturnStatus::Exception);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus::api
|
||||||
70
src/dBus/api/log.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
#include <sdi_toolBox/dateTime/age.h>
|
||||||
|
|
||||||
|
namespace api::log
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Log levels
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class LogLevel : uint8_t
|
||||||
|
{
|
||||||
|
Message = 0,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
|
||||||
|
System, // Reserved for system-level logs, not intended for user messages
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Log message structure
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct LogMessage : dBus::api::DefaultData<dBus::makeID("log.message")>
|
||||||
|
{
|
||||||
|
using Timestamp = std::chrono::steady_clock::time_point;
|
||||||
|
|
||||||
|
virtual ~LogMessage() = default;
|
||||||
|
|
||||||
|
Timestamp timestamp = std::chrono::steady_clock::now();
|
||||||
|
LogLevel level = LogLevel::Message;
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toString() const override
|
||||||
|
{
|
||||||
|
const auto timestampFormatter = Age(std::chrono::duration_cast<std::chrono::nanoseconds>(timestamp.time_since_epoch()));
|
||||||
|
const auto logEntry = std::format("{}{}",
|
||||||
|
getLevelString(),
|
||||||
|
message);
|
||||||
|
return logEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] std::string getLevelString() const
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
using enum LogLevel;
|
||||||
|
case Debug:
|
||||||
|
return "[Debug] ";
|
||||||
|
case Info:
|
||||||
|
return "[Info] ";
|
||||||
|
case Warning:
|
||||||
|
return "[Warning] ";
|
||||||
|
case Error:
|
||||||
|
return "[Error] ";
|
||||||
|
case System:
|
||||||
|
return "[System] ";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace api::log
|
||||||
50
src/dBus/api/logWrapper.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "logWrapper.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postMessage(dBus::Bus &bus, const api::log::LogLevel logLevel, const std::string &message)
|
||||||
|
{
|
||||||
|
api::log::LogMessage logMsg;
|
||||||
|
logMsg.level = logLevel;
|
||||||
|
logMsg.message = message;
|
||||||
|
|
||||||
|
// Post the log message to the bus with the appropriate category (Fire-and-forget, no response expected)
|
||||||
|
(void)dBus::api::postData(bus, logMsg.getID(), logMsg, dBus::MessageCategory::Log);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogMessage(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::Message, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogDebug(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::Debug, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogInfo(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::Info, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogWarning(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::Warning, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogError(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::Error, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void api::log::postLogSystem(dBus::Bus &bus, const std::string &message)
|
||||||
|
{
|
||||||
|
postMessage(bus, LogLevel::System, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
24
src/dBus/api/logWrapper.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../bus.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace api::log
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Post event helpers
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogMessage(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogDebug(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogInfo(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogWarning(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogError(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void postLogSystem(dBus::Bus &bus, const std::string &message);
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace api::log
|
||||||
274
src/dBus/bus.cpp
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#include "bus.h"
|
||||||
|
|
||||||
|
#include "dbus.h"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dBus;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
Bus::Bus()
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
m_startTimestamp = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// Start the monitoring thread
|
||||||
|
m_monitoringThread = jthread([&](const stop_token &st)
|
||||||
|
{
|
||||||
|
while (!st.stop_requested())
|
||||||
|
{
|
||||||
|
this_thread::sleep_for(100ms);
|
||||||
|
updateMonitoringData();
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the timestamp of when the bus started */
|
||||||
|
TimePoint Bus::getStartTimestamp() const
|
||||||
|
{
|
||||||
|
return m_startTimestamp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Subscribe a listener to a specific event type */
|
||||||
|
void Bus::subscribe(const HashID &eventType, Node *node)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Event type not registered yet, create a new entry with the node
|
||||||
|
if (!m_routingTable.nodes.contains(eventType))
|
||||||
|
{
|
||||||
|
m_routingTable.nodes[eventType] = { node };
|
||||||
|
m_routingTable.activeNodesCache.insert(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event type already registered, add the node if not already subscribed
|
||||||
|
auto &nodes = m_routingTable.nodes.at(eventType);
|
||||||
|
if (std::ranges::find(nodes, node) == nodes.end())
|
||||||
|
{
|
||||||
|
nodes.push_back(node);
|
||||||
|
m_routingTable.activeNodesCache.insert(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unsubscribe a listener from a specific event type */
|
||||||
|
void Bus::unsubscribe(const HashID &eventType, Node *node)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Event type not recorded, nothing to do
|
||||||
|
if (!m_routingTable.nodes.contains(eventType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Event type registered, remove the listener if subscribed
|
||||||
|
auto &nodes = m_routingTable.nodes.at(eventType);
|
||||||
|
std::erase(nodes, node);
|
||||||
|
|
||||||
|
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
|
||||||
|
if (!isSubscribedEverywhere(node))
|
||||||
|
m_routingTable.activeNodesCache.erase(node);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unsubscribe a listener from all event types */
|
||||||
|
void Bus::unsubscribeAll(Node *node)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Iterate through all event types and remove the node from each list
|
||||||
|
for (auto &nodeList : m_routingTable.nodes | std::views::values)
|
||||||
|
{
|
||||||
|
std::erase(nodeList, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
|
||||||
|
if (!isSubscribedEverywhere(node))
|
||||||
|
m_routingTable.activeNodesCache.erase(node);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if a listener is subscribed to a specific event type */
|
||||||
|
bool Bus::isSubscribed(const HashID &eventType, const Node *node) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Event type not recorded, node not subscribed
|
||||||
|
if (!m_routingTable.nodes.contains(eventType))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Event type recorded, check if the node is in the list
|
||||||
|
const auto &nodes = m_routingTable.nodes.at(eventType);
|
||||||
|
return std::ranges::find(nodes, node) != nodes.end();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Subscribe a node to receive all messages (broadcast mode) */
|
||||||
|
void Bus::subscribeToBroadcast(Node *node)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Add the node to the broadcast nodes set
|
||||||
|
m_routingTable.broadcastNodes.insert(node);
|
||||||
|
m_routingTable.activeNodesCache.insert(node);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unsubscribe a node from broadcast mode */
|
||||||
|
void Bus::unsubscribeFromBroadcast(Node *node)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Remove the node from the broadcast nodes set
|
||||||
|
m_routingTable.broadcastNodes.erase(node);
|
||||||
|
|
||||||
|
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
|
||||||
|
if (!isSubscribedEverywhere(node))
|
||||||
|
m_routingTable.activeNodesCache.erase(node);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if a node is subscribed to broadcast mode */
|
||||||
|
bool Bus::isSubscribedToBroadcast(Node *node) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Check if the node is in the broadcast nodes set
|
||||||
|
return m_routingTable.broadcastNodes.contains(node);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool Bus::post(const std::shared_ptr<Message> &message)
|
||||||
|
{
|
||||||
|
// Link the message to the bus (set the posted information for the message)
|
||||||
|
message->linkToBus(this);
|
||||||
|
|
||||||
|
// Record the incoming message for monitoring purposes
|
||||||
|
recordIncoming();
|
||||||
|
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx);
|
||||||
|
|
||||||
|
// Get the list of nodes subscribed to the message type
|
||||||
|
set<Node *> recipients;
|
||||||
|
|
||||||
|
// Add nodes subscribed to the specific message type
|
||||||
|
if (m_routingTable.nodes.contains(message->getMessageTypeID()))
|
||||||
|
{
|
||||||
|
const auto &nodeList = m_routingTable.nodes.at(message->getMessageTypeID());
|
||||||
|
for (const auto &node : nodeList)
|
||||||
|
recipients.insert(node);
|
||||||
|
}
|
||||||
|
const bool subscribersFound = !recipients.empty();
|
||||||
|
|
||||||
|
// Add broadcast nodes
|
||||||
|
recipients.insert(m_routingTable.broadcastNodes.begin(),
|
||||||
|
m_routingTable.broadcastNodes.end());
|
||||||
|
|
||||||
|
// Dispatch the message to all recipients
|
||||||
|
for (const auto &node : recipients)
|
||||||
|
node->receiveMessageFromBus(message);
|
||||||
|
|
||||||
|
return subscribersFound;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the current monitoring data of the bus */
|
||||||
|
Bus::MonitoringData Bus::getMonitoringData() const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_monitoring.mtx);
|
||||||
|
return m_monitoring.data;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set the age of the message (to be called by the message when it is destroyed) */
|
||||||
|
void Bus::setMessageAge(std::chrono::microseconds age)
|
||||||
|
{
|
||||||
|
scoped_lock lock(m_monitoring.mtx);
|
||||||
|
|
||||||
|
m_monitoring.data.maxAge = std::max(m_monitoring.data.maxAge, age);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if a node is subscribed to any message type (internal, assumes lock is held) */
|
||||||
|
bool Bus::isSubscribedEverywhere(Node *node) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!node)
|
||||||
|
throw runtime_error("invalid node handle");
|
||||||
|
|
||||||
|
// Check if the node is still subscribed to any message type
|
||||||
|
for (const auto &nodeList : m_routingTable.nodes | std::views::values)
|
||||||
|
{
|
||||||
|
if (std::ranges::find(nodeList, node) != nodeList.end())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the node is subscribed to broadcast mode
|
||||||
|
if (m_routingTable.broadcastNodes.contains(node))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Incoming messages for monitoring purposes */
|
||||||
|
void Bus::recordIncoming()
|
||||||
|
{
|
||||||
|
m_monitoring.messageCounter++;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update the monitoring data of the bus */
|
||||||
|
void Bus::updateMonitoringData()
|
||||||
|
{
|
||||||
|
if (!m_monitoring.frequencyTimer.isElapsed(10s))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Update monitoring data
|
||||||
|
MonitoringData monitoringData;
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_routingTable.mtx, m_monitoring.mtx);
|
||||||
|
|
||||||
|
// Update the global message count with the number of messages received in the last interval
|
||||||
|
m_monitoring.data.globalMessageCount += m_monitoring.messageCounter;
|
||||||
|
|
||||||
|
// Calculate the frequency of messages being posted to the bus (messages per second)
|
||||||
|
const auto duration_s = static_cast<double>(chrono::duration_cast<chrono::milliseconds>(m_monitoring.frequencyTimer.getElapsed()).count()) / 1000.0;
|
||||||
|
m_monitoring.data.frequencyHz = static_cast<double>(m_monitoring.messageCounter) / duration_s;
|
||||||
|
|
||||||
|
// Update the current message count and maximum age of messages on the bus
|
||||||
|
m_monitoring.data.currentMessageCount = 0;
|
||||||
|
for (const auto &node : m_routingTable.activeNodesCache)
|
||||||
|
m_monitoring.data.currentMessageCount += node->getMessageCount();
|
||||||
|
|
||||||
|
// Copy the monitoring data to a local variable for posting the monitoring message after releasing the locks
|
||||||
|
monitoringData = m_monitoring.data;
|
||||||
|
|
||||||
|
// Reset all monitoring counters and timers for the next interval
|
||||||
|
m_monitoring.data.maxAge = {};
|
||||||
|
m_monitoring.messageCounter = 0;
|
||||||
|
m_monitoring.frequencyTimer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post monitoring message to the bus
|
||||||
|
// TODO
|
||||||
|
//postMonitoringMsg(*this, monitoringData);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
83
src/dBus/bus.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Message.h"
|
||||||
|
#include "Node.h"
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
#include <mutex>
|
||||||
|
#include <sdi_toolBox/dateTime/timer.h>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace dBus
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Bus
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct MonitoringData
|
||||||
|
{
|
||||||
|
size_t globalMessageCount{ 0 }; // Total count of messages posted to the bus
|
||||||
|
double frequencyHz = { 0 }; // Current frequency of messages being posted to the bus (calculated as messages per second)
|
||||||
|
|
||||||
|
size_t currentMessageCount{ 0 }; // Current count of messages on the bus
|
||||||
|
std::chrono::microseconds maxAge{}; // Maximum age of messages on the bus
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Bus(); // Default constructor
|
||||||
|
virtual ~Bus() = default; // Default destructor
|
||||||
|
Bus(const Bus &obj) = delete; // Copy constructor
|
||||||
|
Bus(Bus &&obj) noexcept = delete; // Move constructor
|
||||||
|
Bus &operator=(const Bus &obj) = delete; // Copy assignment operator
|
||||||
|
Bus &operator=(Bus &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
[[nodiscard]] TimePoint getStartTimestamp() const; // Get the timestamp of when the bus started
|
||||||
|
|
||||||
|
// Registration and unregistration of listeners
|
||||||
|
void subscribe(const HashID &eventType, Node *node); // Subscribe a listener to a specific event type
|
||||||
|
void unsubscribe(const HashID &eventType, Node *node); // Unsubscribe a listener from a specific event type
|
||||||
|
void unsubscribeAll(Node *node); // Unsubscribe a listener from all event types
|
||||||
|
[[nodiscard]] bool isSubscribed(const HashID &eventType, const Node *node) const; // Check if a listener is subscribed to a specific event type
|
||||||
|
|
||||||
|
// Broadcast management
|
||||||
|
void subscribeToBroadcast(Node *node); // Subscribe a node to receive all messages (broadcast mode)
|
||||||
|
void unsubscribeFromBroadcast(Node *node); // Unsubscribe a node from broadcast mode
|
||||||
|
[[nodiscard]] bool isSubscribedToBroadcast(Node *node) const; // Check if a node is subscribed to broadcast mode
|
||||||
|
|
||||||
|
// Data transmission
|
||||||
|
bool post(const std::shared_ptr<Message> &message); // Post a message to the bus
|
||||||
|
|
||||||
|
// Monitoring
|
||||||
|
MonitoringData getMonitoringData() const; // Get the current monitoring data of the bus (message count, frequency, etc.)
|
||||||
|
void setMessageAge(std::chrono::microseconds age); // Set the age of the message
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TimePoint m_startTimestamp; // Timestamp of when the bus started
|
||||||
|
std::jthread m_monitoringThread; // Thread for monitoring the performance of the bus
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
mutable std::mutex mtx; // Mutex for thread-safe access to the nodes map
|
||||||
|
std::unordered_map<HashID, std::vector<Node *>> nodes; // Map of message type IDs to lists of subscribed nodes
|
||||||
|
std::set<Node *> broadcastNodes; // Set of nodes subscribed to receive all messages (broadcast mode)
|
||||||
|
std::set<Node *> activeNodesCache; // Cache of active nodes for quick access during message routing
|
||||||
|
} m_routingTable;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
mutable std::mutex mtx; // Mutex for thread-safe access to the monitoring data
|
||||||
|
MonitoringData data;
|
||||||
|
|
||||||
|
sdi_toolBox::dateTime::Timer frequencyTimer; // Timer for calculating message frequency
|
||||||
|
size_t messageCounter = { 0 }; // Counter for calculating message frequency
|
||||||
|
} m_monitoring;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isSubscribedEverywhere(Node *node) const; // Check if a node is subscribed to any message type (used for managing the active nodes cache)
|
||||||
|
void recordIncoming(); // Incoming messages for monitoring purposes
|
||||||
|
void updateMonitoringData(); // Update the monitoring data of the bus (message count, frequency, etc.)
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus
|
||||||
8
src/dBus/dBus.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#include "bus.h"
|
||||||
|
#include "defs.h"
|
||||||
|
#include "message.h"
|
||||||
|
#include "node.h"
|
||||||
|
//--------------------------------------------------------------
|
||||||
105
src/dBus/defs.h
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace dBus
|
||||||
|
{
|
||||||
|
using HashID = uint32_t;
|
||||||
|
using TimePoint = std::chrono::steady_clock::time_point;
|
||||||
|
|
||||||
|
constexpr auto DEFAULT_TIMEOUT = std::chrono::milliseconds(100);
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// Hash function for IDs using FNV-1a algorithm
|
||||||
|
constexpr HashID makeID(const std::string_view id) noexcept
|
||||||
|
{
|
||||||
|
constexpr uint32_t FNV_offset_basis = 2166136261u;
|
||||||
|
constexpr uint32_t FNV_prime = 16777619u;
|
||||||
|
|
||||||
|
uint32_t hash_value = FNV_offset_basis;
|
||||||
|
for (const char c : id)
|
||||||
|
{
|
||||||
|
hash_value ^= static_cast<uint32_t>(static_cast<uint8_t>(c));
|
||||||
|
hash_value *= FNV_prime;
|
||||||
|
}
|
||||||
|
return hash_value;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct has_to_string : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template<typename T>
|
||||||
|
struct has_to_string<T, std::void_t<decltype(std::to_string(std::declval<T>()))>>
|
||||||
|
: std::true_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
template<typename T>
|
||||||
|
constexpr bool has_to_string_v = has_to_string<T>::value;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class ReturnStatus : uint8_t
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
Timeout,
|
||||||
|
NoSubscribers,
|
||||||
|
Error,
|
||||||
|
Exception,
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class MessageCategory : uint32_t
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Log = 1 << 0,
|
||||||
|
Hardware = 1 << 1,
|
||||||
|
Dataset = 1 << 2,
|
||||||
|
Database = 1 << 3,
|
||||||
|
System = 1 << 4,
|
||||||
|
UI = 1 << 5,
|
||||||
|
All = 0xFFFFFFFF
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert LogCategory to a string representation
|
||||||
|
inline std::string logCategoryToString(const MessageCategory &cat)
|
||||||
|
{
|
||||||
|
static const std::unordered_map<MessageCategory, std::string> names = {
|
||||||
|
{ MessageCategory::None, "None" },
|
||||||
|
{ MessageCategory::Log, "Log" },
|
||||||
|
{ MessageCategory::Hardware, "Hardware" },
|
||||||
|
{ MessageCategory::Dataset, "Dataset" },
|
||||||
|
{ MessageCategory::Database, "Database" },
|
||||||
|
{ MessageCategory::System, "System" },
|
||||||
|
{ MessageCategory::UI, "UI" },
|
||||||
|
{ MessageCategory::All, "All" }
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto it = names.find(cat);
|
||||||
|
return (it != names.end()) ? it->second : "Composite/Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitwise OR operator for LogCategory to allow combining categories
|
||||||
|
inline MessageCategory operator|(const MessageCategory lhs, const MessageCategory rhs)
|
||||||
|
{
|
||||||
|
return static_cast<MessageCategory>(
|
||||||
|
static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compound assignment operator for LogCategory
|
||||||
|
inline MessageCategory &operator|=(MessageCategory &lhs, const MessageCategory rhs)
|
||||||
|
{
|
||||||
|
lhs = lhs | rhs;
|
||||||
|
return lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitwise AND operator for LogCategory to check if a category is included in a combination
|
||||||
|
inline bool operator&(const MessageCategory lhs, const MessageCategory rhs)
|
||||||
|
{
|
||||||
|
return (static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)) != 0;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus
|
||||||
125
src/dBus/message.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "Message.h"
|
||||||
|
|
||||||
|
#include "bus.h"
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#include <sdi_toolBox/dateTime/age.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dBus;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
Message::Message(const HashID &messageTypeID, const MessageCategory &category)
|
||||||
|
{
|
||||||
|
// Default initialization
|
||||||
|
m_postedAt = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// Set the message parameters
|
||||||
|
m_messageTypeID = messageTypeID;
|
||||||
|
m_category = category;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
Message::~Message()
|
||||||
|
{
|
||||||
|
// Update the age of the message (to be used when logging the message in the bus)
|
||||||
|
if (m_bus)
|
||||||
|
{
|
||||||
|
const auto finalAge = getAge();
|
||||||
|
m_bus->setMessageAge(finalAge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the timestamp of when the message was posted */
|
||||||
|
TimePoint Message::getPostedAt() const
|
||||||
|
{
|
||||||
|
return m_postedAt;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set the message parameters when the message is posted to the bus (to be called by the bus when the message is posted) */
|
||||||
|
void Message::linkToBus(Bus *bus)
|
||||||
|
{
|
||||||
|
m_bus = bus;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the age of the message in microseconds */
|
||||||
|
std::chrono::microseconds Message::getAge() const
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const auto age = now - m_postedAt;
|
||||||
|
|
||||||
|
return std::chrono::duration_cast<std::chrono::microseconds>(age);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if the message is of a specific type */
|
||||||
|
bool Message::isType(const HashID &eventType) const
|
||||||
|
{
|
||||||
|
return m_messageTypeID == eventType;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the unique identifier for the message type */
|
||||||
|
HashID Message::getMessageTypeID() const
|
||||||
|
{
|
||||||
|
return m_messageTypeID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the log category of the message */
|
||||||
|
MessageCategory Message::getCategory() const
|
||||||
|
{
|
||||||
|
return m_category;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Serialize the message data to a string */
|
||||||
|
std::string Message::serializeData() const
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get a string representation of the message for logging purposes */
|
||||||
|
std::string Message::toString() const
|
||||||
|
{
|
||||||
|
const auto serializedData = serializeData();
|
||||||
|
|
||||||
|
// Determine the age of the message for logging purposes
|
||||||
|
string age = "N/A";
|
||||||
|
if (m_bus)
|
||||||
|
{
|
||||||
|
const auto &busStartTimestamp = m_bus->getStartTimestamp();
|
||||||
|
const auto dt = m_postedAt - busStartTimestamp;
|
||||||
|
age = Age(dt).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format message output
|
||||||
|
auto str = std::format("[{:}] - 0x{:08x} ({:})",
|
||||||
|
age,
|
||||||
|
m_messageTypeID,
|
||||||
|
logCategoryToString(m_category));
|
||||||
|
if (!serializedData.empty())
|
||||||
|
str += " - " + serializedData;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get a formatted log line representation of the message for logging purposes */
|
||||||
|
std::string Message::toLogLine() const
|
||||||
|
{
|
||||||
|
const auto serializedData = serializeData();
|
||||||
|
|
||||||
|
// Determine the age of the message for logging purposes
|
||||||
|
std::chrono::microseconds age{ 0 };
|
||||||
|
if (m_bus)
|
||||||
|
{
|
||||||
|
const auto &busStartTimestamp = m_bus->getStartTimestamp();
|
||||||
|
const auto dt = m_postedAt - busStartTimestamp;
|
||||||
|
age = Age(dt).getMicroseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format message output
|
||||||
|
auto str = std::format("[{:}] - 0x{:08x} ({:})",
|
||||||
|
age.count(),
|
||||||
|
m_messageTypeID,
|
||||||
|
logCategoryToString(m_category));
|
||||||
|
if (!serializedData.empty())
|
||||||
|
str += " - " + serializedData;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
152
src/dBus/message.h
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
namespace dBus
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Message : public std::enable_shared_from_this<Message>
|
||||||
|
{
|
||||||
|
friend class Bus;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Message() = default; // Default constructor
|
||||||
|
virtual ~Message(); // Default destructor
|
||||||
|
Message(const Message &obj) = delete; // Copy constructor
|
||||||
|
Message(Message &&obj) noexcept = delete; // Move constructor
|
||||||
|
Message &operator=(const Message &obj) = delete; // Copy assignment operator
|
||||||
|
Message &operator=(Message &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Message(const HashID &messageTypeID, // Constructor
|
||||||
|
const MessageCategory &category);
|
||||||
|
|
||||||
|
// Message management
|
||||||
|
TimePoint getPostedAt() const; // Get the timestamp of when the message was posted
|
||||||
|
[[nodiscard]] std::chrono::microseconds getAge() const; // Get the age of the message in microseconds
|
||||||
|
[[nodiscard]] bool isType(const HashID &eventType) const; // Check if the message is of a specific type
|
||||||
|
|
||||||
|
// Trace information
|
||||||
|
[[nodiscard]] HashID getMessageTypeID() const; // Get the unique identifier for the message type
|
||||||
|
[[nodiscard]] MessageCategory getCategory() const; // Get the log category of the message
|
||||||
|
|
||||||
|
[[nodiscard]] virtual std::string serializeData() const; // Serialize the message data to a string
|
||||||
|
[[nodiscard]] virtual std::string toString() const; // Get a string representation of the message for logging purposes
|
||||||
|
[[nodiscard]] virtual std::string toLogLine() const; // Get a formatted log line representation of the message for logging purposes
|
||||||
|
|
||||||
|
template<class Derived>
|
||||||
|
[[nodiscard]] std::shared_ptr<Derived> as(); // Template method to cast the message to a specific derived type
|
||||||
|
template<class Derived>
|
||||||
|
[[nodiscard]] std::shared_ptr<const Derived> as() const; // Template method to cast the message to a specific derived type
|
||||||
|
|
||||||
|
protected:
|
||||||
|
HashID m_messageTypeID = 0; // Unique identifier for the message type
|
||||||
|
MessageCategory m_category = MessageCategory::None; // Log category of the message
|
||||||
|
|
||||||
|
Bus *m_bus = nullptr; // Pointer to the bus this message is posted to (to be set by the bus when the message is posted)
|
||||||
|
TimePoint m_postedAt; // Timestamp of when the message was posted
|
||||||
|
|
||||||
|
private:
|
||||||
|
void linkToBus(Bus *bus); // Set the message parameters when the message is posted to the bus
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Template method to cast the message to a specific derived type */
|
||||||
|
template<class Derived>
|
||||||
|
std::shared_ptr<Derived> Message::as()
|
||||||
|
{
|
||||||
|
return std::dynamic_pointer_cast<Derived>(shared_from_this());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Template method to cast the message to a specific derived type */
|
||||||
|
template<class Derived>
|
||||||
|
std::shared_ptr<const Derived> Message::as() const
|
||||||
|
{
|
||||||
|
return std::dynamic_pointer_cast<const Derived>(shared_from_this());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class T>
|
||||||
|
class EventMessage : public Message
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
T value; // Value associated with the event message
|
||||||
|
|
||||||
|
EventMessage() = delete; // Default constructor
|
||||||
|
virtual ~EventMessage() = default; // Default destructor
|
||||||
|
EventMessage(const EventMessage &obj) = delete; // Copy constructor
|
||||||
|
EventMessage(EventMessage &&obj) noexcept = delete; // Move constructor
|
||||||
|
EventMessage &operator=(const EventMessage &obj) = delete; // Copy assignment operator
|
||||||
|
EventMessage &operator=(EventMessage &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit EventMessage(const HashID &messageTypeID, // Constructor
|
||||||
|
const MessageCategory &category,
|
||||||
|
T v);
|
||||||
|
|
||||||
|
[[nodiscard]] std::string serializeData() const override; // Serialize the message data to a string
|
||||||
|
};
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
template<class T>
|
||||||
|
EventMessage<T>::EventMessage(const HashID &messageTypeID, const MessageCategory &category, T v)
|
||||||
|
: Message(messageTypeID, category)
|
||||||
|
, value(std::move(v))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Serialize the message data to a string */
|
||||||
|
template<class T>
|
||||||
|
std::string EventMessage<T>::serializeData() const
|
||||||
|
{
|
||||||
|
if constexpr (std::is_arithmetic_v<T>)
|
||||||
|
return std::to_string(value);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class T, class U>
|
||||||
|
class RequestMessage : public Message
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
T value; // Value associated with the event message
|
||||||
|
std::promise<U> responsePromise; // Promise to be fulfilled with the response data once the request is processed
|
||||||
|
|
||||||
|
RequestMessage() = delete; // Default constructor
|
||||||
|
virtual ~RequestMessage() = default; // Default destructor
|
||||||
|
RequestMessage(const RequestMessage &obj) = delete; // Copy constructor
|
||||||
|
RequestMessage(RequestMessage &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequestMessage &operator=(const RequestMessage &obj) = delete; // Copy assignment operator
|
||||||
|
RequestMessage &operator=(RequestMessage &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequestMessage(const HashID &messageTypeID, // Constructor
|
||||||
|
const MessageCategory &category,
|
||||||
|
T v,
|
||||||
|
std::shared_ptr<U> p);
|
||||||
|
|
||||||
|
std::future<U> getFuture(); // Get the future associated with the response promise
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
template<class T, class U>
|
||||||
|
RequestMessage<T, U>::RequestMessage(const HashID &messageTypeID, const MessageCategory &category, T v, std::shared_ptr<U> p)
|
||||||
|
: Message(messageTypeID, category)
|
||||||
|
, value(std::move(v))
|
||||||
|
, responsePromise(std::promise<U>{})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the future associated with the response promise */
|
||||||
|
template<class T, class U>
|
||||||
|
std::future<U> RequestMessage<T, U>::getFuture()
|
||||||
|
{
|
||||||
|
return std::move(responsePromise.get_future());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus
|
||||||
121
src/dBus/node.cpp
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#include "node.h"
|
||||||
|
|
||||||
|
#include "bus.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dBus;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
Node::Node(Bus &bus)
|
||||||
|
: m_bus(bus)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
Node::~Node()
|
||||||
|
{
|
||||||
|
unsubscribeAll();
|
||||||
|
|
||||||
|
// Notify the node thread to stop waiting for messages and exit the loop (if applicable)
|
||||||
|
notifyMessageQueue();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Subscribe to a specific message type */
|
||||||
|
void Node::subscribe(const HashID &eventType)
|
||||||
|
{
|
||||||
|
m_bus.subscribe(eventType, this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unsubscribe from a specific message type */
|
||||||
|
void Node::unsubscribe(const HashID &eventType)
|
||||||
|
{
|
||||||
|
m_bus.unsubscribe(eventType, this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Unsubscribe from all message types */
|
||||||
|
void Node::unsubscribeAll()
|
||||||
|
{
|
||||||
|
m_bus.unsubscribeAll(this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if is subscribed to a specific message type */
|
||||||
|
bool Node::isSubscribed(const HashID &eventType) const
|
||||||
|
{
|
||||||
|
return m_bus.isSubscribed(eventType, this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Wait for a message to be received */
|
||||||
|
void Node::syncWaitForMessage()
|
||||||
|
{
|
||||||
|
// Check if there are already messages in the queue before waiting
|
||||||
|
// to avoid missing notifications if a message is received between
|
||||||
|
// the last check and the wait call
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_dBusMessages.mtx);
|
||||||
|
if (!m_dBusMessages.messageQueue.empty())
|
||||||
|
return; // Messages already in the queue, no need to wait
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until a message is received (the bus will call notifyMessageFromBus()
|
||||||
|
// to wake up the node thread when a message is posted)
|
||||||
|
m_nodeWaitFlag = false;
|
||||||
|
m_nodeWaitFlag.wait(false);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Post a message to the bus */
|
||||||
|
void Node::postMessage(const std::shared_ptr<Message> &message) const
|
||||||
|
{
|
||||||
|
m_bus.post(message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the maximum age of messages in the queue */
|
||||||
|
std::chrono::microseconds Node::getMessageMaxAge() const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_dBusMessages.mtx);
|
||||||
|
|
||||||
|
// If the queue is empty, return a default value (e.g., zero)
|
||||||
|
if (m_dBusMessages.messageQueue.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Get the age of the oldest message in the queue
|
||||||
|
// We can assume that the messages are ordered by age, so we can check the front of the queue
|
||||||
|
const auto &oldestMessage = m_dBusMessages.messageQueue.front();
|
||||||
|
return oldestMessage->getAge();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the number of received messages */
|
||||||
|
size_t Node::getMessageCount() const
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_dBusMessages.mtx);
|
||||||
|
return m_dBusMessages.messageQueue.size();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the next message from the queue */
|
||||||
|
std::shared_ptr<Message> Node::popNextMessage()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_dBusMessages.mtx);
|
||||||
|
if (m_dBusMessages.messageQueue.empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto event = m_dBusMessages.messageQueue.front();
|
||||||
|
m_dBusMessages.messageQueue.pop();
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Receive a message from the bus */
|
||||||
|
void Node::receiveMessageFromBus(std::shared_ptr<Message> message)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(m_dBusMessages.mtx);
|
||||||
|
m_dBusMessages.messageQueue.push(std::move(message));
|
||||||
|
|
||||||
|
notifyMessageQueue();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Notify the node of a new message from the bus */
|
||||||
|
void Node::notifyMessageQueue()
|
||||||
|
{
|
||||||
|
m_nodeWaitFlag.store(true);
|
||||||
|
m_nodeWaitFlag.notify_one();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
57
src/dBus/node.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Message.h"
|
||||||
|
#include "defs.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace dBus
|
||||||
|
{
|
||||||
|
class Bus;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Node
|
||||||
|
{
|
||||||
|
friend class Bus;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Node() = delete; // Default constructor
|
||||||
|
virtual ~Node(); // Default destructor
|
||||||
|
Node(const Node &obj) = delete; // Copy constructor
|
||||||
|
Node(Node &&obj) noexcept = delete; // Move constructor
|
||||||
|
Node &operator=(const Node &obj) = delete; // Copy assignment operator
|
||||||
|
Node &operator=(Node &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Node(Bus &bus); // Constructor
|
||||||
|
|
||||||
|
// Event subscription management
|
||||||
|
void subscribe(const HashID &eventType); // Subscribe to a specific message type
|
||||||
|
void unsubscribe(const HashID &eventType); // Unsubscribe from a specific message type
|
||||||
|
void unsubscribeAll(); // Unsubscribe from all message types
|
||||||
|
[[nodiscard]] bool isSubscribed(const HashID &eventType) const; // Check if is subscribed to a specific message type
|
||||||
|
|
||||||
|
void syncWaitForMessage(); // Wait for a message to be received
|
||||||
|
|
||||||
|
// Data transmission
|
||||||
|
void postMessage(const std::shared_ptr<Message> &message) const; // Post a message to the bus
|
||||||
|
|
||||||
|
[[nodiscard]] std::chrono::microseconds getMessageMaxAge() const; // Get the maximum age of messages in the queue
|
||||||
|
[[nodiscard]] size_t getMessageCount() const; // Get the number of received messages
|
||||||
|
[[nodiscard]] std::shared_ptr<Message> popNextMessage(); // Get the next message from the queue
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void receiveMessageFromBus(std::shared_ptr<Message> message); // Receive a message from the bus (to be called by the bus when a message is posted)
|
||||||
|
void notifyMessageQueue(); // Notify the node of a new message from the bus
|
||||||
|
|
||||||
|
Bus &m_bus; // Reference to the bus this node is connected to
|
||||||
|
|
||||||
|
std::atomic_bool m_nodeWaitFlag = false; // Flag to control the node thread loop
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
mutable std::mutex mtx; // Mutex for thread-safe access to the event queue
|
||||||
|
std::queue<std::shared_ptr<Message>> messageQueue; // Queue of received events
|
||||||
|
} m_dBusMessages;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus
|
||||||
39
src/gui/bars/menuBar.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "menuBar.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
MenuBar::MenuBar(wxFrame *parent)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
parent->SetMenuBar(this);
|
||||||
|
|
||||||
|
// Creating menus
|
||||||
|
createMenus();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Create menus */
|
||||||
|
void MenuBar::createMenus()
|
||||||
|
{
|
||||||
|
// File menu
|
||||||
|
const auto fileMenu = new wxMenu();
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_NEW), _("New\tCTRL+N"), _("Create a new project"));
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_OPEN), _("Open...\tCTRL+O"), _("Open a project file"));
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_SAVE), _("Save\tCTRL+S"), _("Save the project file"));
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_SAVE_AS), _("Save as...\tCTRL+SHIFT+S"), _("Save the project to a new file"));
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_CLOSE), _("Close\tCTRL+F4"), _("Close the project"));
|
||||||
|
fileMenu->AppendSeparator();
|
||||||
|
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_QUIT), _("Quit\tALT+F4"), _("Quit the application"));
|
||||||
|
Append(fileMenu, _("File"));
|
||||||
|
|
||||||
|
// Requirements menu
|
||||||
|
const auto reqMenu = new wxMenu();
|
||||||
|
reqMenu->Append(static_cast<int>(IDs::ID_MENU_REQ_CREATE), _("Create Requirements\tCTRL+R"), _("Create a new requirement"));
|
||||||
|
Append(reqMenu, _("Requirements"));
|
||||||
|
|
||||||
|
// Help menu
|
||||||
|
const auto helpMenu = new wxMenu();
|
||||||
|
helpMenu->Append(static_cast<int>(IDs::ID_MENU_HELP_ABOUT), _("About\tF1"), _("Show info about the application"));
|
||||||
|
Append(helpMenu, _("Help"));
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
41
src/gui/bars/menuBar.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class MenuBar : public wxMenuBar
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class IDs : uint16_t
|
||||||
|
{
|
||||||
|
// File menu
|
||||||
|
ID_MENU_FILE_NEW = wxID_HIGHEST + 1, // File->New project
|
||||||
|
ID_MENU_FILE_OPEN, // File->Open project
|
||||||
|
ID_MENU_FILE_SAVE, // File->Save project
|
||||||
|
ID_MENU_FILE_SAVE_AS, // File->Save project as
|
||||||
|
ID_MENU_FILE_CLOSE, // File->Close project
|
||||||
|
ID_MENU_FILE_QUIT, // File->Quit
|
||||||
|
|
||||||
|
// Requirements menu
|
||||||
|
ID_MENU_REQ_CREATE, // Requirements->Add
|
||||||
|
|
||||||
|
// Help menu
|
||||||
|
ID_MENU_HELP_ABOUT, // Help->About
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
MenuBar() = delete; // Default constructor
|
||||||
|
explicit MenuBar(wxFrame *parent); // Constructor
|
||||||
|
virtual ~MenuBar() = default; // Default destructor
|
||||||
|
MenuBar(MenuBar &obj) = delete; // Copy constructor
|
||||||
|
MenuBar(MenuBar &&obj) = delete; // Move constructor
|
||||||
|
MenuBar &operator=(const MenuBar &obj) = delete; // Copy assignment operator
|
||||||
|
MenuBar &operator=(MenuBar &&obj) = delete; // Move assignment operator
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createMenus(); // Create menus
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
11
src/gui/bars/statusBar.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include "statusBar.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
StatusBar::StatusBar(wxFrame *parent)
|
||||||
|
: wxStatusBar(parent)
|
||||||
|
{
|
||||||
|
parent->SetStatusBar(this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
20
src/gui/bars/statusBar.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class StatusBar : public wxStatusBar
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StatusBar() = delete; // Default constructor
|
||||||
|
explicit StatusBar(wxFrame *parent); // Constructor
|
||||||
|
virtual ~StatusBar() = default; // Default destructor
|
||||||
|
StatusBar(StatusBar &obj) = delete; // Copy constructor
|
||||||
|
StatusBar(StatusBar &&obj) = delete; // Move constructor
|
||||||
|
StatusBar &operator=(const StatusBar &obj) = delete; // Copy assignment operator
|
||||||
|
StatusBar &operator=(StatusBar &&obj) = delete; // Move assignment operator
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
23
src/gui/bars/toolBar.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "toolBar.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
ToolBar::ToolBar(wxWindow *parent)
|
||||||
|
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
const auto minSize = wxSize(-1, 64);
|
||||||
|
SetMinSize(minSize);
|
||||||
|
SetSize(minSize);
|
||||||
|
|
||||||
|
// Creating menus
|
||||||
|
createTools();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Create tools */
|
||||||
|
void ToolBar::createTools()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
23
src/gui/bars/toolBar.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ToolBar : public wxPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ToolBar() = delete; // Default constructor
|
||||||
|
explicit ToolBar(wxWindow *parent); // Constructor
|
||||||
|
virtual ~ToolBar() = default; // Default destructor
|
||||||
|
ToolBar(ToolBar &obj) = delete; // Copy constructor
|
||||||
|
ToolBar(ToolBar &&obj) = delete; // Move constructor
|
||||||
|
ToolBar &operator=(const ToolBar &obj) = delete; // Copy assignment operator
|
||||||
|
ToolBar &operator=(ToolBar &&obj) = delete; // Move assignment operator
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createTools(); // Create tools
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
276
src/gui/mainFrame.cpp
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
#include "mainFrame.h"
|
||||||
|
|
||||||
|
#include "bars/menuBar.h"
|
||||||
|
#include "bars/statusBar.h"
|
||||||
|
#include "bars/toolBar.h"
|
||||||
|
#include "requirementsPanel/requirementsPanel.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
#include "api/project.h"
|
||||||
|
#include "api/requirement.h"
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
MainFrame::MainFrame(dBus::Bus &bus)
|
||||||
|
: wxFrame(nullptr, wxID_ANY, _("kwa.Fr"), wxDefaultPosition, wxDefaultSize)
|
||||||
|
, dBus::wxNode(this, bus)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
SetIcon(wxICON(AAAA_ICON));
|
||||||
|
|
||||||
|
const wxSize minSize(1024, 768);
|
||||||
|
const wxSize maxSize(1200, -1);
|
||||||
|
SetMinSize(minSize);
|
||||||
|
SetMaxSize(maxSize);
|
||||||
|
SetSize(minSize);
|
||||||
|
|
||||||
|
// Creating controls
|
||||||
|
createControls();
|
||||||
|
|
||||||
|
// Post-initialization
|
||||||
|
m_menuBar->Bind(wxEVT_MENU, &MainFrame::on_menuBarItemClick, this);
|
||||||
|
|
||||||
|
CenterOnScreen();
|
||||||
|
|
||||||
|
subscribe(dBus::makeID("log.message"));
|
||||||
|
|
||||||
|
// Iconize();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls */
|
||||||
|
void MainFrame::createControls()
|
||||||
|
{
|
||||||
|
m_menuBar = new MenuBar(this);
|
||||||
|
m_statusBar = new StatusBar(this);
|
||||||
|
|
||||||
|
const auto framePanel = new wxPanel(this /*, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER*/);
|
||||||
|
m_toolBar = new ToolBar(framePanel);
|
||||||
|
|
||||||
|
m_pageNotebook = new wxNotebook(framePanel, wxID_ANY);
|
||||||
|
|
||||||
|
m_requirementsPanel = new RequirementsPanel(m_pageNotebook, m_bus);
|
||||||
|
m_requirementsPanel->SetBackgroundColour(framePanel->GetBackgroundColour());
|
||||||
|
|
||||||
|
m_pageNotebook->AddPage(m_requirementsPanel, "Requirements", true);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
mainSizer->Add(m_toolBar, wxSizerFlags(0).Expand());
|
||||||
|
mainSizer->Add(m_pageNotebook, wxSizerFlags(1).Expand().Border(wxALL, 5));
|
||||||
|
|
||||||
|
framePanel->SetSizer(mainSizer);
|
||||||
|
Layout();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Create a new project */
|
||||||
|
void MainFrame::createProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Create);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for project creation request.\n\n"
|
||||||
|
"The manager responsible for handling project creation "
|
||||||
|
"did not respond to the request. Please ensure that "
|
||||||
|
"the project management component is running and try again.",
|
||||||
|
"Failed...",
|
||||||
|
wxOK | wxICON_WARNING,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxMessageBox("An unexpected error occurred while creating a new project\n\n" + wxString(e.what()),
|
||||||
|
"Critical Error",
|
||||||
|
wxOK | wxICON_ERROR,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Open an existing project */
|
||||||
|
void MainFrame::openProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
wxFileDialog dlg(this,
|
||||||
|
_("Open Project"),
|
||||||
|
wxEmptyString,
|
||||||
|
wxEmptyString,
|
||||||
|
"Project Files (*.kwaProj)|*.kwaProj|All Files (*.*)|*.*",
|
||||||
|
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||||
|
if (dlg.ShowModal() != wxID_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto filepath = filesystem::path(dlg.GetPath().ToStdString());
|
||||||
|
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Open, filepath);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for project opening request.\n\n"
|
||||||
|
"The manager responsible for handling project opening "
|
||||||
|
"did not respond to the request. Please ensure that "
|
||||||
|
"the project management component is running and try again.",
|
||||||
|
"Failed...",
|
||||||
|
wxOK | wxICON_WARNING,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxMessageBox("An unexpected error occurred while opening a project\n\n" + wxString(e.what()),
|
||||||
|
"Critical Error",
|
||||||
|
wxOK | wxICON_ERROR,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Save the current project */
|
||||||
|
void MainFrame::saveProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Save);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for project saving request.\n\n"
|
||||||
|
"The manager responsible for handling project saving "
|
||||||
|
"did not respond to the request. Please ensure that "
|
||||||
|
"the project management component is running and try again.",
|
||||||
|
"Failed...",
|
||||||
|
wxOK | wxICON_WARNING,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxMessageBox("An unexpected error occurred while saving the project\n\n" + wxString(e.what()),
|
||||||
|
"Critical Error",
|
||||||
|
wxOK | wxICON_ERROR,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Save the current project as... */
|
||||||
|
void MainFrame::saveProjectAs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
wxFileDialog dlg(this,
|
||||||
|
_("Save Project"),
|
||||||
|
wxEmptyString,
|
||||||
|
wxEmptyString,
|
||||||
|
"Project Files (*.kwaProj)|*.kwaProj|All Files (*.*)|*.*",
|
||||||
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||||
|
if (dlg.ShowModal() != wxID_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto filepath = filesystem::path(dlg.GetPath().ToStdString());
|
||||||
|
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::SaveAs, filepath);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for project saving request.\n\n"
|
||||||
|
"The manager responsible for handling project saving "
|
||||||
|
"did not respond to the request. Please ensure that "
|
||||||
|
"the project management component is running and try again.",
|
||||||
|
"Failed...",
|
||||||
|
wxOK | wxICON_WARNING,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxMessageBox("An unexpected error occurred while saving the project\n\n" + wxString(e.what()),
|
||||||
|
"Critical Error",
|
||||||
|
wxOK | wxICON_ERROR,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Close the current project */
|
||||||
|
void MainFrame::closeProject()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Close);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for project closing request.\n\n"
|
||||||
|
"The manager responsible for handling project closing "
|
||||||
|
"did not respond to the request. Please ensure that "
|
||||||
|
"the project management component is running and try again.",
|
||||||
|
"Failed...",
|
||||||
|
wxOK | wxICON_WARNING,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxMessageBox("An unexpected error occurred while closing the project\n\n" + wxString(e.what()),
|
||||||
|
"Critical Error",
|
||||||
|
wxOK | wxICON_ERROR,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Menu bar item click event */
|
||||||
|
void MainFrame::on_menuBarItemClick(wxCommandEvent &event)
|
||||||
|
{
|
||||||
|
switch (static_cast<MenuBar::IDs>(event.GetId()))
|
||||||
|
{
|
||||||
|
using enum MenuBar::IDs;
|
||||||
|
// File menu
|
||||||
|
case ID_MENU_FILE_NEW:
|
||||||
|
{
|
||||||
|
createProject();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID_MENU_FILE_OPEN:
|
||||||
|
{
|
||||||
|
openProject();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID_MENU_FILE_SAVE:
|
||||||
|
{
|
||||||
|
saveProject();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID_MENU_FILE_SAVE_AS:
|
||||||
|
{
|
||||||
|
saveProjectAs();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID_MENU_FILE_CLOSE:
|
||||||
|
{
|
||||||
|
closeProject();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ID_MENU_FILE_QUIT:
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requirement menu
|
||||||
|
case ID_MENU_REQ_CREATE:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help menu
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* dBus message reception handler */
|
||||||
|
void MainFrame::on_receiveMessageFromBus(const MessageID messageID, const MessagePtr message)
|
||||||
|
{
|
||||||
|
cout << "From MainFrame::on_receiveMessageFromBus: " << message->toString() << endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
53
src/gui/mainFrame.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wxNode.h"
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <wx/notebook.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
class MenuBar;
|
||||||
|
class StatusBar;
|
||||||
|
class ToolBar;
|
||||||
|
class RequirementsPanel;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class MainFrame : public wxFrame
|
||||||
|
, public dBus::wxNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MainFrame() = delete; // Default constructor
|
||||||
|
virtual ~MainFrame() = default; // Default destructor
|
||||||
|
MainFrame(const MainFrame &obj) = delete; // Copy constructor
|
||||||
|
MainFrame(MainFrame &&obj) noexcept = delete; // Move constructor
|
||||||
|
MainFrame &operator=(const MainFrame &obj) = delete; // Copy assignment operator
|
||||||
|
MainFrame &operator=(MainFrame &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit MainFrame(dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Controls
|
||||||
|
MenuBar *m_menuBar = nullptr; // Menu bar handle
|
||||||
|
StatusBar *m_statusBar = nullptr; // Status bar handle
|
||||||
|
ToolBar *m_toolBar = nullptr; // Toolbar handle
|
||||||
|
wxNotebook *m_pageNotebook = nullptr; // Main notebook handle
|
||||||
|
|
||||||
|
RequirementsPanel *m_requirementsPanel = nullptr; // Requirements panel handle
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createControls(); // Creating controls
|
||||||
|
|
||||||
|
void createProject(); // Create a new project
|
||||||
|
void openProject(); // Open an existing project
|
||||||
|
void saveProject(); // Save the current project
|
||||||
|
void saveProjectAs(); // Save the current project as...
|
||||||
|
void closeProject(); // Close the current project
|
||||||
|
|
||||||
|
// Events
|
||||||
|
public:
|
||||||
|
void on_menuBarItemClick(wxCommandEvent &event); // Menu bar item click event
|
||||||
|
void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) override; // dBus message reception handler
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include "requirementDetailPanel.h"
|
||||||
|
|
||||||
|
#include "resources/resources.h"
|
||||||
|
|
||||||
|
#include <wx/statline.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
RequirementDetailPanel::RequirementDetailPanel(wxWindow *parentWindow, dBus::Bus &bus)
|
||||||
|
: wxPanel(parentWindow /*, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER*/)
|
||||||
|
, m_bus(bus)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
|
||||||
|
// Creating controls
|
||||||
|
createControls();
|
||||||
|
|
||||||
|
// Post initialization
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls */
|
||||||
|
void RequirementDetailPanel::createControls()
|
||||||
|
{
|
||||||
|
const auto mainPanel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||||
|
mainPanel->SetScrollRate(0, 10); // Set the scroll rate for the panel (vertical only)
|
||||||
|
|
||||||
|
const auto closeButton = new wxBitmapButton(this, wxID_ANY, icon_xmark_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
|
||||||
|
const auto saveButton = new wxBitmapButton(this, wxID_ANY, icon_floppy_disk_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
|
||||||
|
const auto discardButton = new wxBitmapButton(this, wxID_ANY, icon_rotate_left_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto ctrlSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
ctrlSizer->Add(createControls_metadata(mainPanel), wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP, 5));
|
||||||
|
ctrlSizer->Add(createControls_details(mainPanel), wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP, 5));
|
||||||
|
ctrlSizer->Add(createControls_classification(mainPanel), wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP & ~wxBOTTOM, 5));
|
||||||
|
mainPanel->SetSizer(ctrlSizer);
|
||||||
|
|
||||||
|
const auto buttonSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
buttonSizer->Add(closeButton, wxSizerFlags(0).Expand());
|
||||||
|
buttonSizer->AddStretchSpacer(1);
|
||||||
|
buttonSizer->Add(discardButton, wxSizerFlags(0).Expand().Border(wxBOTTOM, 5));
|
||||||
|
buttonSizer->Add(saveButton, wxSizerFlags(0).Expand());
|
||||||
|
|
||||||
|
const auto mainSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
mainSizer->Add(mainPanel, wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT, 5));
|
||||||
|
mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand().Border(wxALL, 5));
|
||||||
|
|
||||||
|
SetSizer(mainSizer);
|
||||||
|
|
||||||
|
// Set the virtual size to match the size of the child panel,
|
||||||
|
// enabling scrolling if necessary
|
||||||
|
mainPanel->FitInside();
|
||||||
|
|
||||||
|
// Harmonize the sizes of the title controls to ensure they have the same width
|
||||||
|
updateTitleSizes();
|
||||||
|
Layout();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls for requirement metadata (ID, UUID, name, description, etc.) */
|
||||||
|
wxSizer *RequirementDetailPanel::createControls_metadata(wxWindow *owner)
|
||||||
|
{
|
||||||
|
const auto uuidTitleCtrl = createTitle(owner, _T("UUID:"));
|
||||||
|
const auto idTitleCtrl = createTitle(owner, _T("ID:"));
|
||||||
|
const auto authorTitleCtrl = createTitle(owner, _("Author:"));
|
||||||
|
const auto createdAtTitleCtrl = createTitle(owner, _("Created at:"));
|
||||||
|
const auto updatedAtTitleCtrl = createTitle(owner, _("Updated at:"));
|
||||||
|
|
||||||
|
const auto uuidValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto idValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto authorValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto createdAtValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto updatedAtValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5);
|
||||||
|
ctrlSizer->Add(uuidTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(uuidValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(idTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(idValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(authorTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(authorValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(createdAtTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(createdAtValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(updatedAtTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(updatedAtValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->AddGrowableCol(1, 1);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto localSizer = new wxStaticBoxSizer(wxVERTICAL, owner, "Metadata");
|
||||||
|
localSizer->Add(ctrlSizer, wxSizerFlags(0).Expand().Border(wxALL, 5));
|
||||||
|
|
||||||
|
return localSizer;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls for requirement details (e.g., custom attributes, child requirements, etc.) */
|
||||||
|
wxSizer *RequirementDetailPanel::createControls_details(wxWindow *owner)
|
||||||
|
{
|
||||||
|
const auto titleTitleCtrl = createTitle(owner, _("Title:"));
|
||||||
|
const auto descriptionTitleCtrl = createTitle(owner, _("Description:"));
|
||||||
|
const auto acceptanceCriteriaTitleCtrl = createTitle(owner, _("Acceptance criteria:"));
|
||||||
|
|
||||||
|
const auto titleValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto descriptionValueCtrl = new wxTextCtrl(owner, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_TAB | wxTE_MULTILINE);
|
||||||
|
const auto acceptanceCriteriaValueCtrl = new wxTextCtrl(owner, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_TAB | wxTE_MULTILINE);
|
||||||
|
const auto smartCheckBoxCtrl = new wxCheckBox(owner, wxID_ANY, _("S.M.A.R.T."));
|
||||||
|
|
||||||
|
descriptionValueCtrl->SetMinSize(wxSize(-1, 200));
|
||||||
|
acceptanceCriteriaValueCtrl->SetMinSize(wxSize(-1, 100));
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5);
|
||||||
|
ctrlSizer->Add(titleTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(titleValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(descriptionTitleCtrl, wxSizerFlags(0).Expand());
|
||||||
|
ctrlSizer->Add(descriptionValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(acceptanceCriteriaTitleCtrl, wxSizerFlags(0).Expand());
|
||||||
|
ctrlSizer->Add(acceptanceCriteriaValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->AddStretchSpacer();
|
||||||
|
ctrlSizer->Add(smartCheckBoxCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->AddGrowableCol(1, 1);
|
||||||
|
|
||||||
|
const auto localSizer = new wxStaticBoxSizer(wxVERTICAL, owner, "Details");
|
||||||
|
localSizer->Add(ctrlSizer, wxSizerFlags(0).Expand().Border(wxALL, 5));
|
||||||
|
|
||||||
|
return localSizer;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls for requirement classification (e.g., priority, severity, etc.) */
|
||||||
|
wxSizer *RequirementDetailPanel::createControls_classification(wxWindow *owner)
|
||||||
|
{
|
||||||
|
const auto typeTitleCtrl = createTitle(owner, _("Type:"));
|
||||||
|
const auto categoryTitleCtrl = createTitle(owner, _("Category:"));
|
||||||
|
const auto priorityTitleCtrl = createTitle(owner, _("Priority:"));
|
||||||
|
const auto statusTitleCtrl = createTitle(owner, _("Status:"));
|
||||||
|
|
||||||
|
const auto typeValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto categoryValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto priorityValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
const auto statusValueCtrl = new wxTextCtrl(owner, wxID_ANY);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5);
|
||||||
|
ctrlSizer->Add(typeTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(typeValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(categoryTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(categoryValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(priorityTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(priorityValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->Add(statusTitleCtrl, wxSizerFlags(0).Expand().CenterVertical());
|
||||||
|
ctrlSizer->Add(statusValueCtrl, wxSizerFlags(1).Expand());
|
||||||
|
ctrlSizer->AddGrowableCol(1, 1);
|
||||||
|
|
||||||
|
const auto localSizer = new wxStaticBoxSizer(wxVERTICAL, owner, "Classification");
|
||||||
|
localSizer->Add(ctrlSizer, wxSizerFlags(0).Expand().Border(wxALL, 5));
|
||||||
|
|
||||||
|
return localSizer;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Helper function to create a section title control */
|
||||||
|
wxStaticText *RequirementDetailPanel::createTitle(wxWindow *owner, const wxString &title)
|
||||||
|
{
|
||||||
|
const auto ctrl = new wxStaticText(owner,
|
||||||
|
wxID_ANY,
|
||||||
|
title,
|
||||||
|
wxDefaultPosition,
|
||||||
|
wxDefaultSize,
|
||||||
|
wxST_NO_AUTORESIZE | wxALIGN_RIGHT);
|
||||||
|
ctrl->SetFont(GetFont().Italic());
|
||||||
|
|
||||||
|
m_titleControls.push_back(ctrl);
|
||||||
|
|
||||||
|
return ctrl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Harmonize the sizes of the title controls to ensure they have the same width */
|
||||||
|
void RequirementDetailPanel::updateTitleSizes() const
|
||||||
|
{
|
||||||
|
int maxWidth = 0;
|
||||||
|
for (const auto &ctrl : m_titleControls)
|
||||||
|
{
|
||||||
|
maxWidth = std::max(maxWidth, ctrl->GetSize().GetWidth());
|
||||||
|
}
|
||||||
|
for (const auto &ctrl : m_titleControls)
|
||||||
|
{
|
||||||
|
ctrl->SetMinSize(wxSize(maxWidth, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementDetailPanel : public wxPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequirementDetailPanel() = delete; // Default constructor
|
||||||
|
virtual ~RequirementDetailPanel() = default; // Default destructor
|
||||||
|
RequirementDetailPanel(const RequirementDetailPanel &obj) = delete; // Copy constructor
|
||||||
|
RequirementDetailPanel(RequirementDetailPanel &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementDetailPanel &operator=(const RequirementDetailPanel &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementDetailPanel &operator=(RequirementDetailPanel &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequirementDetailPanel(wxWindow *parentWindow, dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
dBus::Bus &m_bus; // Reference to the application bus
|
||||||
|
std::vector<wxControl *> m_titleControls; // Titles controls for each section (metadata, details, classification)
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createControls(); // Creating controls
|
||||||
|
wxSizer *createControls_metadata(wxWindow *owner); // Creating controls for requirement metadata
|
||||||
|
wxSizer *createControls_details(wxWindow *owner); // Creating controls for requirement details
|
||||||
|
wxSizer *createControls_classification(wxWindow *owner); // Creating controls for requirement classification
|
||||||
|
|
||||||
|
wxStaticText *createTitle(wxWindow *owner, const wxString &title); // Helper function to create a section title control
|
||||||
|
void updateTitleSizes() const; // Harmonize the sizes of the title controls to ensure they have the same width
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#include "requirementListPanel.h"
|
||||||
|
|
||||||
|
#include "requirementTreeModel.h"
|
||||||
|
#include "resources/resources.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
RequirementListPanel::RequirementListPanel(wxWindow *parentWindow, dBus::Bus &bus)
|
||||||
|
: wxPanel(parentWindow, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxSIMPLE_BORDER)
|
||||||
|
, m_bus(bus)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
|
||||||
|
// Creating controls
|
||||||
|
createControls();
|
||||||
|
|
||||||
|
// Post initialization
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls */
|
||||||
|
void RequirementListPanel::createControls()
|
||||||
|
{
|
||||||
|
// Create the data view control
|
||||||
|
m_dataViewCtrl = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
|
||||||
|
m_dataViewCtrl->AppendTextColumn(_T("ID"), 0, wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT);
|
||||||
|
m_dataViewCtrl->AppendTextColumn(_("Title"), 1, wxDATAVIEW_CELL_INERT, 200, wxALIGN_LEFT);
|
||||||
|
|
||||||
|
const auto model = new RequirementTreeModel(m_bus);
|
||||||
|
model->loadRootRequirement();
|
||||||
|
m_dataViewCtrl->AssociateModel(model);
|
||||||
|
model->DecRef(); // The control now owns the model, so we can release our reference
|
||||||
|
|
||||||
|
// Create other controls
|
||||||
|
const auto addButton = new wxBitmapButton(this, wxID_ANY, icon_plus_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
|
||||||
|
addButton->Bind(wxEVT_BUTTON, &RequirementListPanel::on_addButtonClick, this);
|
||||||
|
|
||||||
|
const auto reloadButton = new wxBitmapButton(this, wxID_ANY, icon_rotate_left_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
|
||||||
|
reloadButton->Bind(wxEVT_BUTTON, &RequirementListPanel::on_reloadButtonClick, this);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto buttonSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
buttonSizer->Add(addButton, wxSizerFlags(0).Border(wxALL, 5));
|
||||||
|
buttonSizer->AddStretchSpacer();
|
||||||
|
buttonSizer->Add(reloadButton, wxSizerFlags(0).Border(wxALL, 5));
|
||||||
|
|
||||||
|
const auto mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
mainSizer->Add(m_dataViewCtrl, wxSizerFlags(1).Expand().Border(wxALL, 5));
|
||||||
|
mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand());
|
||||||
|
|
||||||
|
SetSizer(mainSizer);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process the add requirement click event */
|
||||||
|
void RequirementListPanel::on_addButtonClick(wxCommandEvent &event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process the reload requirements click event */
|
||||||
|
void RequirementListPanel::on_reloadButtonClick(wxCommandEvent &event)
|
||||||
|
{
|
||||||
|
// Reload all requirements
|
||||||
|
|
||||||
|
if (auto *model = dynamic_cast<RequirementTreeModel *>(m_dataViewCtrl->GetModel()))
|
||||||
|
model->loadRootRequirement();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <wx/dataview.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementListPanel : public wxPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequirementListPanel() = delete; // Default constructor
|
||||||
|
virtual ~RequirementListPanel() = default; // Default destructor
|
||||||
|
RequirementListPanel(const RequirementListPanel &obj) = delete; // Copy constructor
|
||||||
|
RequirementListPanel(RequirementListPanel &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementListPanel &operator=(const RequirementListPanel &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementListPanel &operator=(RequirementListPanel &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequirementListPanel(wxWindow *parentWindow, dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
dBus::Bus &m_bus; // Reference to the application bus
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
wxDataViewCtrl *m_dataViewCtrl = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createControls(); // Creating controls
|
||||||
|
|
||||||
|
// Events
|
||||||
|
void on_addButtonClick(wxCommandEvent &event); // Process the add requirement click event
|
||||||
|
void on_reloadButtonClick(wxCommandEvent &event); // Process the reload requirements click event
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#include "requirementTreeModel.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
RequirementTreeModel::RequirementTreeModel(dBus::Bus &bus)
|
||||||
|
: m_bus(bus)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void RequirementTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, const unsigned col) const
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!item.IsOk())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto *data = static_cast<RequirementData *>(item.GetID());
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Return the appropriate value based on the column index
|
||||||
|
switch (col)
|
||||||
|
{
|
||||||
|
case 0: // ID column
|
||||||
|
variant = data->id;
|
||||||
|
break;
|
||||||
|
case 1: // Title column
|
||||||
|
variant = data->title;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool RequirementTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned col)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
wxDataViewItem RequirementTreeModel::GetParent(const wxDataViewItem &item) const
|
||||||
|
{
|
||||||
|
if (!item.IsOk())
|
||||||
|
return wxDataViewItem(nullptr);
|
||||||
|
|
||||||
|
// Get the requirement data associated with the item
|
||||||
|
const auto *data = static_cast<RequirementData *>(item.GetID());
|
||||||
|
if (!data)
|
||||||
|
return wxDataViewItem(nullptr); // If the item is invalid, return an invalid item
|
||||||
|
if (data == &m_rootRequirement)
|
||||||
|
return wxDataViewItem(nullptr); // If the item is the root requirement, return an invalid item
|
||||||
|
if (!data->parent)
|
||||||
|
return wxDataViewItem(nullptr); // If the item has no parent, return an invalid item
|
||||||
|
|
||||||
|
return wxDataViewItem(data->parent);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool RequirementTreeModel::IsContainer(const wxDataViewItem &item) const
|
||||||
|
{
|
||||||
|
const auto *data = static_cast<RequirementData *>(item.GetID());
|
||||||
|
if (!data)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !data->children.empty(); // An item is a container if it has children
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool RequirementTreeModel::HasContainerColumns(const wxDataViewItem &wxDataViewItem) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
unsigned int RequirementTreeModel::GetChildren(const wxDataViewItem &item, wxDataViewItemArray &children) const
|
||||||
|
{
|
||||||
|
const auto *data = static_cast<RequirementData *>(item.GetID());
|
||||||
|
if (!data) // If the item is invalid, return the root requirement as the only child
|
||||||
|
{
|
||||||
|
children.Add(wxDataViewItem(const_cast<RequirementData *>(&m_rootRequirement)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the children of the current item to the children array
|
||||||
|
for (const auto &child : data->children)
|
||||||
|
{
|
||||||
|
children.Add(wxDataViewItem(child.get()));
|
||||||
|
}
|
||||||
|
return static_cast<uint32_t>(data->children.size());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void RequirementTreeModel::loadRootRequirement()
|
||||||
|
{
|
||||||
|
// Get the root requirements from dBus
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto ret = api::requirement::postGetRequirementRequest(m_bus);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
wxMessageBox("No manager found for requirement request.\n\n"
|
||||||
|
"Error loading root requirements.",
|
||||||
|
"Error",
|
||||||
|
wxOK | wxICON_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rootRequirement = RequirementData(*ret);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
// Handle the error (e.g., log it, show a message to the user, etc.)
|
||||||
|
// For now, we just print the error message to the console
|
||||||
|
std::cerr << "Error loading root requirements: " << e.what() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the control that the data has changed
|
||||||
|
//ItemChanged(wxDataViewItem(nullptr)); // Notify that the entire tree has changed
|
||||||
|
//ItemsAdded(wxDataViewItem(nullptr), wxDataViewItemArray{ wxDataViewItem(&m_rootRequirement) });
|
||||||
|
Cleared();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
RequirementData *RequirementTreeModel::AddChildRequirement(RequirementData *parent, std::unique_ptr<RequirementData> requirementData)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void RequirementTreeModel::clear()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
RequirementData *RequirementTreeModel::GetRequirementData(const wxDataViewItem &item) const
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <api/requirement.h>
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <wx/dataview.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
struct RequirementData
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string title;
|
||||||
|
|
||||||
|
RequirementData *parent = nullptr;
|
||||||
|
std::vector<std::unique_ptr<RequirementData>> children;
|
||||||
|
|
||||||
|
RequirementData()
|
||||||
|
{
|
||||||
|
id = "badID";
|
||||||
|
title = "badTITLE";
|
||||||
|
}
|
||||||
|
RequirementData(const api::requirement::Requirement &requirement)
|
||||||
|
{
|
||||||
|
id = "testID";
|
||||||
|
title = "testTITLE";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a child requirement
|
||||||
|
RequirementData *AddChild(std::unique_ptr<RequirementData> child)
|
||||||
|
{
|
||||||
|
child->parent = this;
|
||||||
|
children.push_back(std::move(child));
|
||||||
|
return children.back().get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementTreeModel : public wxDataViewModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequirementTreeModel() = delete; // Default constructor
|
||||||
|
virtual ~RequirementTreeModel() = default; // Default destructor
|
||||||
|
RequirementTreeModel(const RequirementTreeModel &obj) = delete; // Copy constructor
|
||||||
|
RequirementTreeModel(RequirementTreeModel &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementTreeModel &operator=(const RequirementTreeModel &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementTreeModel &operator=(RequirementTreeModel &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequirementTreeModel(dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
// wxDataViewModel overrides methods
|
||||||
|
void GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned col) const override;
|
||||||
|
bool SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned col) override;
|
||||||
|
|
||||||
|
[[nodiscard]] wxDataViewItem GetParent(const wxDataViewItem &item) const override;
|
||||||
|
[[nodiscard]] bool IsContainer(const wxDataViewItem &item) const override;
|
||||||
|
[[nodiscard]] bool HasContainerColumns(const wxDataViewItem &) const override;
|
||||||
|
|
||||||
|
unsigned int GetChildren(const wxDataViewItem &item, wxDataViewItemArray &children) const override;
|
||||||
|
|
||||||
|
// Tree data model specific methods
|
||||||
|
void loadRootRequirement(); // Add a root requirement to the tree
|
||||||
|
|
||||||
|
RequirementData *AddChildRequirement(RequirementData *parent, std::unique_ptr<RequirementData> requirementData); // Add a child requirement to a parent requirement
|
||||||
|
void clear();
|
||||||
|
RequirementData *GetRequirementData(const wxDataViewItem &item) const; // Get the requirement data associated with a given item
|
||||||
|
|
||||||
|
protected:
|
||||||
|
dBus::Bus &m_bus; // Reference to the application bus
|
||||||
|
|
||||||
|
// std::vector<std::unique_ptr<RequirementData>> m_rootRequirement;
|
||||||
|
RequirementData m_rootRequirement;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
43
src/gui/requirementsPanel/requirementsPanel.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include "requirementsPanel.h"
|
||||||
|
|
||||||
|
#include "requirementDetailPanel/requirementDetailPanel.h"
|
||||||
|
#include "requirementListPanel/requirementListPanel.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace gui;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
RequirementsPanel::RequirementsPanel(wxWindow *parentWindow, dBus::Bus &bus)
|
||||||
|
: wxPanel(parentWindow, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER)
|
||||||
|
, dBus::wxNode(this, bus)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||||
|
SetOwnBackgroundColour(GetBackgroundColour());
|
||||||
|
|
||||||
|
// Creating controls
|
||||||
|
createControls();
|
||||||
|
|
||||||
|
// Post initialization
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Creating controls */
|
||||||
|
void RequirementsPanel::createControls()
|
||||||
|
{
|
||||||
|
m_requirementListPanel = new RequirementListPanel(this, m_bus);
|
||||||
|
m_requirementDetailPanel = new RequirementDetailPanel(this, m_bus);
|
||||||
|
|
||||||
|
// Controls positioning
|
||||||
|
const auto mainSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
mainSizer->Add(m_requirementListPanel, wxSizerFlags(0).Expand().Border(wxALL, 5));
|
||||||
|
mainSizer->Add(m_requirementDetailPanel, wxSizerFlags(1).Expand().Border(wxALL, 5));
|
||||||
|
//mainSizer->AddStretchSpacer(1);
|
||||||
|
|
||||||
|
SetSizer(mainSizer);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* dBus message reception handler */
|
||||||
|
void RequirementsPanel::on_receiveMessageFromBus(MessageID messageID, MessagePtr message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
68
src/gui/requirementsPanel/requirementsPanel.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "gui/wxNode.h"
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
namespace gui
|
||||||
|
{
|
||||||
|
class RequirementDetailPanel;
|
||||||
|
class RequirementListPanel;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class RequirementsPanel : public wxPanel
|
||||||
|
, public dBus::wxNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RequirementsPanel() = delete; // Default constructor
|
||||||
|
virtual ~RequirementsPanel() = default; // Default destructor
|
||||||
|
RequirementsPanel(const RequirementsPanel &obj) = delete; // Copy constructor
|
||||||
|
RequirementsPanel(RequirementsPanel &&obj) noexcept = delete; // Move constructor
|
||||||
|
RequirementsPanel &operator=(const RequirementsPanel &obj) = delete; // Copy assignment operator
|
||||||
|
RequirementsPanel &operator=(RequirementsPanel &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit RequirementsPanel(wxWindow *parentWindow, dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Controls
|
||||||
|
RequirementListPanel *m_requirementListPanel = nullptr; // Pointer to the requirement list panel control
|
||||||
|
RequirementDetailPanel *m_requirementDetailPanel = nullptr; // Pointer to the requirement detail panel control
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createControls(); // Creating controls
|
||||||
|
|
||||||
|
// Events
|
||||||
|
void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) override; // dBus message reception handler
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace gui
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ ▼ Métadonnées │
|
||||||
|
│ ID: [REQ-001] 🔒 │
|
||||||
|
│ UUID: [abc-123-def] 🔒 │
|
||||||
|
│ Auteur: [John Doe] 🔒 │
|
||||||
|
│ Créé le: [2026-03-13 14:30] 🔒 │
|
||||||
|
│ Modifié le: [2026-03-13 15:45] 🔒 │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ ▼ Détails │
|
||||||
|
│ Nom: [________________] │
|
||||||
|
│ Description: [ ] │
|
||||||
|
│ [ ] │
|
||||||
|
│ [________________] │
|
||||||
|
│ Critères: [ ] │
|
||||||
|
│ [________________] │
|
||||||
|
│ □ SMART │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ ▼ Classification │
|
||||||
|
│ Type: [Functional ▼] │
|
||||||
|
│ Catégorie: [Backend ▼] │
|
||||||
|
│ Priorité: ● ● ● ○ ○ (3/5) │
|
||||||
|
│ Statut: ○ Draft ○ Review │
|
||||||
|
│ ● Approved ○ Rejected │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ [Enregistrer] [Annuler] │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
*/
|
||||||
159
src/gui/wxNode.cpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#include "wxNode.h"
|
||||||
|
|
||||||
|
wxDEFINE_EVENT(EVT_DBUS_EVENT_MESSAGE, wxDBusCommandEvent);
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dBus;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
wxDBusCommandEvent::wxDBusCommandEvent(const dBus::HashID messageTypeID, MessagePtr message)
|
||||||
|
: wxCommandEvent(EVT_DBUS_EVENT_MESSAGE, 0)
|
||||||
|
, m_messageTypeID(messageTypeID)
|
||||||
|
, m_message(std::move(message))
|
||||||
|
{
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Clone method for wxWidgets event system */
|
||||||
|
wxEvent *wxDBusCommandEvent::Clone() const
|
||||||
|
{
|
||||||
|
auto *clonedEvent = new wxDBusCommandEvent(m_messageTypeID, m_message);
|
||||||
|
clonedEvent->SetEventObject(GetEventObject()); // Copy the event object
|
||||||
|
return clonedEvent;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Return the message type ID */
|
||||||
|
HashID wxDBusCommandEvent::getMessageType() const
|
||||||
|
{
|
||||||
|
return m_messageTypeID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set the message type ID */
|
||||||
|
void wxDBusCommandEvent::setDbusEventType(const dBus::HashID &messageTypeID)
|
||||||
|
{
|
||||||
|
m_messageTypeID = messageTypeID;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set the message type ID based on event type string */
|
||||||
|
void wxDBusCommandEvent::makeDbusEventType(const std::string &eventType)
|
||||||
|
{
|
||||||
|
m_messageTypeID = makeID(eventType);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Check if the event has an associated message */
|
||||||
|
bool wxDBusCommandEvent::hasMessage() const
|
||||||
|
{
|
||||||
|
return m_message != nullptr;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the associated message casted to a specific type */
|
||||||
|
wxDBusCommandEvent::MessagePtr wxDBusCommandEvent::getMessage() const
|
||||||
|
{
|
||||||
|
return m_message;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Set the associated message for the event */
|
||||||
|
void wxDBusCommandEvent::setMessage(const MessagePtr &message)
|
||||||
|
{
|
||||||
|
m_message = message;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
dBus::wxNode::wxNode(wxWindow *owner, dBus::Bus &bus)
|
||||||
|
: Node(bus)
|
||||||
|
, m_owner(owner)
|
||||||
|
{
|
||||||
|
// Sanity check
|
||||||
|
if (!m_owner)
|
||||||
|
throw std::invalid_argument("Owner window cannot be null");
|
||||||
|
|
||||||
|
// Bind the custom event handler to the owner window
|
||||||
|
m_owner->Bind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);
|
||||||
|
|
||||||
|
// Start the event thread to listen for messages from the bus
|
||||||
|
startEventThread();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Destructor */
|
||||||
|
dBus::wxNode::~wxNode()
|
||||||
|
{
|
||||||
|
// Stop the event thread to clean up resources
|
||||||
|
stopEventThread();
|
||||||
|
|
||||||
|
// Unbind the custom event handler from the owner window
|
||||||
|
if (m_owner)
|
||||||
|
m_owner->Unbind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Start the event thread to listen for messages from the bus */
|
||||||
|
void dBus::wxNode::startEventThread()
|
||||||
|
{
|
||||||
|
// Start thread to process events related to the channel.
|
||||||
|
// The thread will run until stop is requested.
|
||||||
|
// clang-format off
|
||||||
|
m_eventThread = std::jthread([this](const std::stop_token &stopToken)
|
||||||
|
{
|
||||||
|
// Process events until stop is requested
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
eventThreadLoop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the event thread */
|
||||||
|
void dBus::wxNode::stopEventThread()
|
||||||
|
{
|
||||||
|
// Stop event thread loop
|
||||||
|
m_eventThread.request_stop();
|
||||||
|
|
||||||
|
// Wake up event thread if waiting
|
||||||
|
notifyMessageQueue();
|
||||||
|
|
||||||
|
// Ensure thread has fully stopped before unbinding
|
||||||
|
if (m_eventThread.joinable())
|
||||||
|
m_eventThread.join();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* The main loop of the event thread that listens for messages
|
||||||
|
* from the bus and posts events to the wxWidgets event system */
|
||||||
|
void dBus::wxNode::eventThreadLoop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Wait for a message to be received
|
||||||
|
syncWaitForMessage();
|
||||||
|
|
||||||
|
// Process all messages in the queue
|
||||||
|
while (getMessageCount() > 0)
|
||||||
|
{
|
||||||
|
const auto message = popNextMessage();
|
||||||
|
if (!message)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto event = wxDBusCommandEvent(message->getMessageTypeID(), message);
|
||||||
|
wxQueueEvent(m_owner, event.Clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
wxLogError("Exception in wxDBus event thread: %s", e.what());
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Handler for wxDBusCommandEvent events posted to the GUI,
|
||||||
|
* extracts the message and calls on_receiveMessageFromBus */
|
||||||
|
void dBus::wxNode::on_DBusEvent(wxDBusCommandEvent &event)
|
||||||
|
{
|
||||||
|
const auto &messageID = event.getMessageType();
|
||||||
|
const auto &message = event.getMessage();
|
||||||
|
|
||||||
|
on_receiveMessageFromBus(messageID, message);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
85
src/gui/wxNode.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dBus/dBus.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class wxDBusCommandEvent : public wxCommandEvent
|
||||||
|
{
|
||||||
|
using MessagePtr = std::shared_ptr<dBus::Message>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
wxDBusCommandEvent() = delete; // Default constructor
|
||||||
|
virtual ~wxDBusCommandEvent() = default; // Default destructor
|
||||||
|
wxDBusCommandEvent(const wxDBusCommandEvent &obj) = delete; // Copy constructor
|
||||||
|
wxDBusCommandEvent(wxDBusCommandEvent &&obj) noexcept = delete; // Move constructor
|
||||||
|
wxDBusCommandEvent &operator=(const wxDBusCommandEvent &obj) = delete; // Copy assignment operator
|
||||||
|
wxDBusCommandEvent &operator=(wxDBusCommandEvent &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit wxDBusCommandEvent(dBus::HashID messageTypeID, // Constructor
|
||||||
|
MessagePtr message = {});
|
||||||
|
|
||||||
|
[[nodiscard]] wxEvent *Clone() const override; // Clone method for wxWidgets event system
|
||||||
|
|
||||||
|
// dBus message type management
|
||||||
|
[[nodiscard]] dBus::HashID getMessageType() const; // Return the message type ID
|
||||||
|
void setDbusEventType(const dBus::HashID &messageTypeID); // Set the message type ID
|
||||||
|
void makeDbusEventType(const std::string &eventType); // Set the message type ID based on event type string
|
||||||
|
|
||||||
|
// dBus message payload management
|
||||||
|
[[nodiscard]] bool hasMessage() const; // Check if the event has an associated message
|
||||||
|
[[nodiscard]] MessagePtr getMessage() const; // Get the associated message casted to a specific type
|
||||||
|
void setMessage(const MessagePtr &message); // Set the associated message for the event
|
||||||
|
|
||||||
|
protected:
|
||||||
|
dBus::HashID m_messageTypeID; // Message type ID
|
||||||
|
MessagePtr m_message; // Store the original dBus message
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
wxDECLARE_EVENT(EVT_DBUS_EVENT_MESSAGE, wxDBusCommandEvent);
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
namespace dBus
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class wxNode : private dBus::Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MessageID = dBus::HashID;
|
||||||
|
using MessagePtr = std::shared_ptr<dBus::Message>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
wxNode() = delete; // Default constructor
|
||||||
|
virtual ~wxNode(); // Default destructor
|
||||||
|
wxNode(const wxNode &obj) = delete; // Copy constructor
|
||||||
|
wxNode(wxNode &&obj) noexcept = delete; // Move constructor
|
||||||
|
wxNode &operator=(const wxNode &obj) = delete; // Copy assignment operator
|
||||||
|
wxNode &operator=(wxNode &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit wxNode(wxWindow *owner, dBus::Bus &bus); // Constructor
|
||||||
|
|
||||||
|
using Node::subscribe; // Subscribe to a specific message type
|
||||||
|
using Node::unsubscribe; // Unsubscribe from a specific message type
|
||||||
|
|
||||||
|
// Message reception handler to be overridden by the gui
|
||||||
|
// derived class to handle incoming messages from the bus
|
||||||
|
virtual void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) = 0;
|
||||||
|
|
||||||
|
using Node::m_bus; // Reference to the bus this node is connected to
|
||||||
|
|
||||||
|
protected:
|
||||||
|
wxWindow *m_owner = nullptr; // Pointer to the owner window (for posting events to the GUI)
|
||||||
|
|
||||||
|
std::jthread m_eventThread; // Thread for listening to messages from the bus
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startEventThread(); // Start the event thread to listen for messages from the bus
|
||||||
|
void stopEventThread(); // Stop the event thread
|
||||||
|
void eventThreadLoop(); // Main loop event thread function
|
||||||
|
|
||||||
|
void on_DBusEvent(wxDBusCommandEvent &event); // Handler for wxDBusCommandEvent events
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace dBus
|
||||||
BIN
src/kwaFr.rc
Normal file
50
src/kwaFr_app.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "kwaFr_app.h"
|
||||||
|
|
||||||
|
#include "gui/mainFrame.h"
|
||||||
|
#include "misc/logConsoleGUI/logConsoleGUI.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <wx/image.h>
|
||||||
|
|
||||||
|
wxIMPLEMENT_APP(kwaFr_app);
|
||||||
|
using namespace std;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
kwaFr_app::~kwaFr_app()
|
||||||
|
{
|
||||||
|
// Clean up the application context
|
||||||
|
m_coreManager.reset();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool kwaFr_app::OnInit()
|
||||||
|
{
|
||||||
|
// Initialize the application context
|
||||||
|
m_coreManager = make_unique<core::CoreManager>();
|
||||||
|
|
||||||
|
// Initialize console
|
||||||
|
logConsole = make_unique<sdiTools::logConsoleGUI>();
|
||||||
|
#ifdef DEBUG
|
||||||
|
constexpr auto attachOnly = false;
|
||||||
|
// logConsole->initConsole();
|
||||||
|
#else
|
||||||
|
constexpr auto attachOnly = true;
|
||||||
|
#endif
|
||||||
|
logConsole->initConsole(attachOnly);
|
||||||
|
|
||||||
|
// wxWidgets configuration
|
||||||
|
wxInitAllImageHandlers();
|
||||||
|
|
||||||
|
// Creating the main frame
|
||||||
|
const auto frame = new gui::MainFrame(m_coreManager->getBus());
|
||||||
|
frame->Show();
|
||||||
|
|
||||||
|
api::log::postLogMessage(m_coreManager->getBus(), "Application started");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
int kwaFr_app::OnExit()
|
||||||
|
{
|
||||||
|
// Application exit
|
||||||
|
return wxApp::OnExit();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
25
src/kwaFr_app.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/coreManager.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <wx/app.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class kwaFr_app : public wxApp
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
kwaFr_app() = default; // Default constructor
|
||||||
|
virtual ~kwaFr_app(); // Default destructor
|
||||||
|
kwaFr_app(const kwaFr_app &obj) = delete; // Copy constructor
|
||||||
|
kwaFr_app(kwaFr_app &&obj) noexcept = delete; // Move constructor
|
||||||
|
kwaFr_app &operator=(const kwaFr_app &obj) = delete; // Copy assignment operator
|
||||||
|
kwaFr_app &operator=(kwaFr_app &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
bool OnInit() override;
|
||||||
|
int OnExit() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<core::CoreManager> m_coreManager; // Core manager instance
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
15
src/misc/json/json.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#include "misc/json/nlohmann/json.hpp"
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using json = nlohmann::ordered_json;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
template<class T>
|
||||||
|
inline T json_readData(const json &jsonData, const std::string &key, const T &defaultValue = {})
|
||||||
|
{
|
||||||
|
if (jsonData.contains(key))
|
||||||
|
return jsonData.at(key).get<T>();
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
24765
src/misc/json/nlohmann/json.hpp
Normal file
176
src/misc/json/nlohmann/json_fwd.hpp
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.11.3
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <map> // map
|
||||||
|
#include <memory> // allocator
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.11.3
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file contains all macro definitions affecting or depending on the ABI
|
||||||
|
|
||||||
|
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||||
|
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
|
||||||
|
#warning "Already included a different version of the library!"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTICS
|
||||||
|
#define JSON_DIAGNOSTICS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Construct the namespace ABI tags component
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
|
||||||
|
|
||||||
|
// Construct the namespace version component
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
_v ## major ## _ ## minor ## _ ## patch
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
||||||
|
|
||||||
|
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_MINOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Combine namespace components
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE \
|
||||||
|
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||||
|
namespace nlohmann \
|
||||||
|
{ \
|
||||||
|
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION) \
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_END \
|
||||||
|
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
||||||
|
} // namespace nlohmann
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief namespace for Niels Lohmann
|
||||||
|
@see https://github.com/nlohmann
|
||||||
|
@since version 1.0.0
|
||||||
|
*/
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default JSONSerializer template argument
|
||||||
|
|
||||||
|
This serializer ignores the template arguments and uses ADL
|
||||||
|
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
|
||||||
|
for serialization.
|
||||||
|
*/
|
||||||
|
template<typename T = void, typename SFINAE = void>
|
||||||
|
struct adl_serializer;
|
||||||
|
|
||||||
|
/// a class to store JSON values
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/
|
||||||
|
template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||||
|
std::map,
|
||||||
|
template<typename U, typename... Args> class ArrayType = std::vector,
|
||||||
|
class StringType = std::string, class BooleanType = bool,
|
||||||
|
class NumberIntegerType = std::int64_t,
|
||||||
|
class NumberUnsignedType = std::uint64_t,
|
||||||
|
class NumberFloatType = double,
|
||||||
|
template<typename U> class AllocatorType = std::allocator,
|
||||||
|
template<typename T, typename SFINAE = void> class JSONSerializer =
|
||||||
|
adl_serializer,
|
||||||
|
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
||||||
|
class CustomBaseClass = void>
|
||||||
|
class basic_json;
|
||||||
|
|
||||||
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||||
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||||
|
template<typename RefStringType>
|
||||||
|
class json_pointer;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default specialization
|
||||||
|
@sa https://json.nlohmann.me/api/json/
|
||||||
|
*/
|
||||||
|
using json = basic_json<>;
|
||||||
|
|
||||||
|
/// @brief a minimal map-like container that preserves insertion order
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_map/
|
||||||
|
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||||
|
struct ordered_map;
|
||||||
|
|
||||||
|
/// @brief specialization that maintains the insertion order of object keys
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_json/
|
||||||
|
using ordered_json = basic_json<nlohmann::ordered_map>;
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
295
src/misc/logConsoleGUI/logConsoleGUI.cpp
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/* --- logConsoleGUI.cpp ---------------------------------------
|
||||||
|
System: Log system for gui applications
|
||||||
|
Status: Version 1.0 Release 2
|
||||||
|
Language: C++
|
||||||
|
|
||||||
|
(c) Copyright SD-Innovation 2016
|
||||||
|
|
||||||
|
Author: Sylvain Schneider
|
||||||
|
------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#include "logConsoleGUI.h"
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <Windows.h>
|
||||||
|
#endif // defined WIN32
|
||||||
|
|
||||||
|
t_logConsoleGUI logConsole;
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace sdiTools;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
logConsoleGUI::logConsoleGUI(const std::string &logFilepath)
|
||||||
|
{
|
||||||
|
// Constructor
|
||||||
|
if (!logFilepath.empty())
|
||||||
|
openLogFile(logFilepath);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
logConsoleGUI::~logConsoleGUI()
|
||||||
|
{
|
||||||
|
// Destructor
|
||||||
|
releaseConsole();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::openLogFile(const std::string &logFilepath)
|
||||||
|
{
|
||||||
|
// Open log file
|
||||||
|
m_logFile.open(logFilepath.c_str(), std::ofstream::out | std::ofstream::app);
|
||||||
|
if (!m_logFile.is_open())
|
||||||
|
throw logic_error("Unable to open log file !");
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::closeLogFile()
|
||||||
|
{
|
||||||
|
// Close log file
|
||||||
|
if (m_logFile.is_open())
|
||||||
|
m_logFile.close();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
int64_t logConsoleGUI::getLogFileSize()
|
||||||
|
{
|
||||||
|
// Return opened log file size
|
||||||
|
if (!m_logFile.is_open())
|
||||||
|
return -1;
|
||||||
|
return m_logFile.tellp();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::initConsole(bool attachOnly)
|
||||||
|
{
|
||||||
|
// Init application console and attach debug console under MSW
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
if (hasAttachedConsole())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool hasParentConsole = true;
|
||||||
|
if (!AttachConsole(ATTACH_PARENT_PROCESS)) // Attach application to console
|
||||||
|
{
|
||||||
|
if (attachOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// No console was available
|
||||||
|
hasParentConsole = false;
|
||||||
|
if (!AllocConsole()) // Create console and attach application to it
|
||||||
|
{
|
||||||
|
// Error during creating console
|
||||||
|
throw logic_error("Unable to attach application to debug console");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int err;
|
||||||
|
err = freopen_s(&m_stdoutFile, "CONOUT$", "w", stdout);
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
// Error during reopen on stdout
|
||||||
|
throw logic_error("Unable to reopen stdout");
|
||||||
|
}
|
||||||
|
err = freopen_s(&m_stderrFile, "CONOUT$", "w", stderr);
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
// Error during reopen on stderr
|
||||||
|
throw logic_error("Unable to reopen stderr");
|
||||||
|
}
|
||||||
|
|
||||||
|
cout.clear();
|
||||||
|
cerr.clear();
|
||||||
|
|
||||||
|
if (hasParentConsole)
|
||||||
|
{
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
// cout << "logConsoleGUI is ready !" << endl;
|
||||||
|
|
||||||
|
#endif // defined WIN32
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::releaseConsole()
|
||||||
|
{
|
||||||
|
// Release application console
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
if (!hasAttachedConsole())
|
||||||
|
return;
|
||||||
|
|
||||||
|
cout << "logConsoleGUI is terminated !" << endl;
|
||||||
|
cout.clear();
|
||||||
|
cerr.clear();
|
||||||
|
|
||||||
|
FreeConsole();
|
||||||
|
|
||||||
|
fclose(m_stdoutFile);
|
||||||
|
fclose(m_stderrFile);
|
||||||
|
|
||||||
|
#endif // defined WIN32
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
bool logConsoleGUI::hasAttachedConsole()
|
||||||
|
{
|
||||||
|
// Returns true if console was attached, false otherwise
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
if (GetConsoleWindow())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#endif // defined WIN32
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::disable_quickEditMode()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
if (hInput == INVALID_HANDLE_VALUE || hInput == NULL)
|
||||||
|
{
|
||||||
|
DWORD errorCode = GetLastError();
|
||||||
|
throw runtime_error("Unable to get input console handle");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL bRet;
|
||||||
|
DWORD consoleMode;
|
||||||
|
|
||||||
|
bRet = GetConsoleMode(hInput, &consoleMode);
|
||||||
|
if (bRet == 0)
|
||||||
|
{
|
||||||
|
DWORD errorCode = GetLastError();
|
||||||
|
throw runtime_error("Unable to get console mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleMode = ENABLE_EXTENDED_FLAGS | (consoleMode & ~ENABLE_QUICK_EDIT_MODE);
|
||||||
|
|
||||||
|
bRet = SetConsoleMode(hInput, consoleMode);
|
||||||
|
if (bRet == 0)
|
||||||
|
{
|
||||||
|
// DWORD errorCode = GetLastError();
|
||||||
|
throw runtime_error("Unable to set console mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable cursor
|
||||||
|
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
if (hOutput == INVALID_HANDLE_VALUE || hOutput == NULL)
|
||||||
|
{
|
||||||
|
DWORD errorCode = GetLastError();
|
||||||
|
throw runtime_error("Unable to get output console handle");
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSOLE_CURSOR_INFO cursorInfo;
|
||||||
|
bRet = GetConsoleCursorInfo(hOutput, &cursorInfo);
|
||||||
|
if (bRet == 0)
|
||||||
|
{
|
||||||
|
throw runtime_error("Unable to get console cursor info");
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorInfo.bVisible = false; // set the cursor visibility
|
||||||
|
bRet = SetConsoleCursorInfo(hOutput, &cursorInfo);
|
||||||
|
if (bRet == 0)
|
||||||
|
{
|
||||||
|
throw runtime_error("Unable to set console cursor info");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // defined WIN32
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::alwaysOnTop()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
HWND hWin = GetConsoleWindow();
|
||||||
|
if (!hWin)
|
||||||
|
throw runtime_error("Unable to get window handle");
|
||||||
|
|
||||||
|
BOOL bRet;
|
||||||
|
|
||||||
|
bRet = SetWindowPos(
|
||||||
|
hWin, // window handle
|
||||||
|
HWND_TOPMOST, // "handle to the window to precede
|
||||||
|
// the positioned window in the Z order
|
||||||
|
// OR one of the following:"
|
||||||
|
// HWND_BOTTOM or HWND_NOTOPMOST or HWND_TOP or HWND_TOPMOST
|
||||||
|
0,
|
||||||
|
80, // X, Y position of the window (in client coordinates)
|
||||||
|
0,
|
||||||
|
0, // cx, cy => width & height of the window in pixels
|
||||||
|
SWP_DRAWFRAME | /* SWP_NOMOVE | */ SWP_NOSIZE | SWP_SHOWWINDOW // The window sizing and positioning flags.
|
||||||
|
);
|
||||||
|
if (bRet == 0)
|
||||||
|
{
|
||||||
|
DWORD errorCode = GetLastError();
|
||||||
|
throw runtime_error("Unable to change window mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // defined WIN32
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void logConsoleGUI::log(const std::string &msg, e_logLevel level)
|
||||||
|
{
|
||||||
|
// Log message into console and log file
|
||||||
|
string levelStr = "";
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case level_DEBUG_COMMUNICATION:
|
||||||
|
#ifndef DEBUG
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
levelStr = "[DEBUG_COMM] ";
|
||||||
|
break;
|
||||||
|
case level_DEBUG:
|
||||||
|
#ifndef DEBUG
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
levelStr = "[DEBUG] ";
|
||||||
|
break;
|
||||||
|
case level_INFORMATIONS:
|
||||||
|
break;
|
||||||
|
case level_NOTICE:
|
||||||
|
levelStr = "[NOTICE] ";
|
||||||
|
break;
|
||||||
|
case level_WARNING:
|
||||||
|
levelStr = "[WARNING] ";
|
||||||
|
break;
|
||||||
|
case level_ERROR:
|
||||||
|
levelStr = "[ERROR] ";
|
||||||
|
break;
|
||||||
|
case level_CRITICAL:
|
||||||
|
levelStr = "[CRITICAL] ";
|
||||||
|
break;
|
||||||
|
case level_ALERT:
|
||||||
|
levelStr = "[ALERT] ";
|
||||||
|
break;
|
||||||
|
case level_EMERGENCY:
|
||||||
|
levelStr = "[EMERGENCY] ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string logMsg = header() + levelStr + msg;
|
||||||
|
|
||||||
|
cout << logMsg << endl;
|
||||||
|
if (m_logFile.is_open())
|
||||||
|
m_logFile << logMsg << endl;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
std::string logConsoleGUI::header()
|
||||||
|
{
|
||||||
|
// Define header to console log
|
||||||
|
char buff[20];
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
struct tm tm;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
localtime_s(&tm, &now);
|
||||||
|
#elif __linux__
|
||||||
|
tm = *localtime(&now);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
strftime(buff, 20, "%Y-%m-%d %H:%M:%S", &tm);
|
||||||
|
|
||||||
|
ostringstream oss;
|
||||||
|
oss << buff << " - ";
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
91
src/misc/logConsoleGUI/logConsoleGUI.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/* --- logConsoleGUI.h -----------------------------------------
|
||||||
|
System: Log system for gui applications
|
||||||
|
Status: Version 1.0 Release 2
|
||||||
|
Language: C++
|
||||||
|
|
||||||
|
(c) Copyright SD-Innovation 2016
|
||||||
|
|
||||||
|
Address:
|
||||||
|
SD-INNOVATION SAS
|
||||||
|
Site Eiffel Energie / ZAC Ban La Dame
|
||||||
|
48, square Eugène Herzog
|
||||||
|
54390 FROUARD
|
||||||
|
FRANCE
|
||||||
|
|
||||||
|
Author: Sylvain Schneider
|
||||||
|
E-Mail: s.schneider@sd-innovation.fr
|
||||||
|
|
||||||
|
Description: Header file for logConsoleGUI
|
||||||
|
This class allows you to attach GUI application to text console
|
||||||
|
or file to write log informations.
|
||||||
|
|
||||||
|
Thread Safe: No
|
||||||
|
Platform Dependencies: None (i.e.: Linux/Intel, IRIX/Mips, Solaris/SPARC)
|
||||||
|
Compiler Options:
|
||||||
|
|
||||||
|
Change History:
|
||||||
|
Date Author Description
|
||||||
|
2016-09-28 Sylvain Schneider Closing the console window when it released
|
||||||
|
2016-08-23 Sylvain Schneider First version
|
||||||
|
------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#ifndef LOGCONSOLEGUI_H
|
||||||
|
#define LOGCONSOLEGUI_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum e_logLevel
|
||||||
|
{
|
||||||
|
level_DEBUG_COMMUNICATION, // Information useful to developers for debugging the communication application
|
||||||
|
level_DEBUG, // Information useful to developers for debugging the application
|
||||||
|
level_INFORMATIONS, // Normal operational messages that require no action (An application has started, paused or ended successfully)
|
||||||
|
level_NOTICE, // Events that are unusual, but not error conditions
|
||||||
|
level_WARNING, // May indicate that an error will occur if action is not taken
|
||||||
|
level_ERROR, // Error conditions
|
||||||
|
level_CRITICAL, // Critical conditions
|
||||||
|
level_ALERT, // Should be corrected immediately
|
||||||
|
level_EMERGENCY, // Application is unusable
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace sdiTools
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class logConsoleGUI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
public:
|
||||||
|
logConsoleGUI(const std::string &logFilepath = ""); // Constructor
|
||||||
|
virtual ~logConsoleGUI(); // Destructor
|
||||||
|
|
||||||
|
void openLogFile(const std::string &logFilepath); // Open log file
|
||||||
|
void closeLogFile(); // Close log file
|
||||||
|
int64_t getLogFileSize(); // Return opened log file size
|
||||||
|
|
||||||
|
void initConsole(bool attachOnly = false); // Init application console
|
||||||
|
void releaseConsole(); // Release application console
|
||||||
|
bool hasAttachedConsole(); // Returns true if console was attached, false otherwise
|
||||||
|
|
||||||
|
void disable_quickEditMode();
|
||||||
|
void alwaysOnTop();
|
||||||
|
|
||||||
|
void log(const std::string &msg, // Log message into console and log file
|
||||||
|
e_logLevel level = level_INFORMATIONS);
|
||||||
|
virtual std::string header(); // Define header to console log
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::ofstream m_logFile;
|
||||||
|
|
||||||
|
FILE *m_stdoutFile = nullptr;
|
||||||
|
FILE *m_stderrFile = nullptr;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace sdiTools
|
||||||
|
|
||||||
|
typedef std::unique_ptr<sdiTools::logConsoleGUI> t_logConsoleGUI;
|
||||||
|
extern t_logConsoleGUI logConsole;
|
||||||
|
|
||||||
|
#endif // LOGCONSOLEGUI_H
|
||||||
17
src/misc/tools.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline uint64_t encodeTimepoint(const std::chrono::system_clock::time_point &timepoint)
|
||||||
|
{
|
||||||
|
const auto duration = timepoint.time_since_epoch();
|
||||||
|
return static_cast<uint64_t>(duration.count());
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline std::chrono::system_clock::time_point decodeTimepoint(const uint64_t value)
|
||||||
|
{
|
||||||
|
const auto duration = std::chrono::system_clock::duration(static_cast<std::chrono::seconds>(value));
|
||||||
|
return std::chrono::system_clock::time_point(duration);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
16
src/misc/wxCustom.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/statline.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticLine *createHorizontalLine(wxWindow *parent, const wxWindowID id = wxID_ANY)
|
||||||
|
{
|
||||||
|
return new wxStaticLine(parent, id, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
inline wxStaticLine *createVerticalLine(wxWindow *parent, const wxWindowID id = wxID_ANY)
|
||||||
|
{
|
||||||
|
return new wxStaticLine(parent, id, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
BIN
src/resources/appIcon.ico
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
src/resources/bin2c.exe
Normal file
BIN
src/resources/icons_24x24/bin2c.exe
Normal file
53
src/resources/icons_24x24/icon_floppy-disk_24x24.png.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto bitmapData = std::to_array<uint8_t>({
|
||||||
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D,
|
||||||
|
0xF8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||||
|
0x8D, 0x01, 0xC6, 0x66, 0xF7, 0xA4, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,
|
||||||
|
0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,
|
||||||
|
0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x01, 0x2F, 0x49,
|
||||||
|
0x44, 0x41, 0x54, 0x48, 0x89, 0xED, 0x95, 0xBD, 0x4A, 0x03, 0x41, 0x14, 0x85, 0xBF, 0x23, 0xAE,
|
||||||
|
0x45, 0xAC, 0x2D, 0x8C, 0x08, 0x5B, 0xD9, 0x68, 0x9E, 0x63, 0x05, 0x7F, 0x4A, 0x5F, 0xC0, 0x37,
|
||||||
|
0xF0, 0x15, 0xEC, 0x6D, 0xC5, 0xC7, 0x88, 0x8D, 0x3F, 0x85, 0x16, 0x1A, 0x5F, 0x41, 0xB0, 0x11,
|
||||||
|
0x04, 0x3B, 0x5B, 0x6D, 0x14, 0x8E, 0x45, 0x76, 0xC3, 0x66, 0x92, 0x38, 0xB3, 0x21, 0x11, 0x0B,
|
||||||
|
0x2F, 0x5C, 0xD8, 0x99, 0x7B, 0xEF, 0x39, 0xCC, 0x9D, 0xD9, 0x73, 0x65, 0x9B, 0x79, 0xDA, 0xC2,
|
||||||
|
0x5C, 0xD1, 0x7F, 0x83, 0x60, 0x31, 0xDC, 0x90, 0x94, 0x01, 0xDB, 0xC0, 0x6A, 0x42, 0xFD, 0x1B,
|
||||||
|
0x70, 0x69, 0xFB, 0x63, 0x62, 0x86, 0xED, 0x81, 0x03, 0x4B, 0xC0, 0x03, 0xE0, 0x06, 0xFE, 0x04,
|
||||||
|
0xE4, 0x75, 0x9C, 0x21, 0xCC, 0x80, 0x60, 0xB7, 0x21, 0x78, 0xE5, 0xCF, 0x93, 0x48, 0xC2, 0x3B,
|
||||||
|
0x68, 0x07, 0xEB, 0x63, 0x20, 0xB3, 0xAD, 0xD0, 0x81, 0x65, 0xA0, 0x5B, 0xE6, 0xE5, 0xC0, 0xAD,
|
||||||
|
0xA4, 0x3C, 0xEC, 0x50, 0xEC, 0x92, 0xCF, 0x6D, 0x7F, 0x8D, 0x0B, 0x94, 0x7D, 0xBF, 0xA8, 0x6D,
|
||||||
|
0xE5, 0xC0, 0x95, 0xA4, 0x56, 0x13, 0x82, 0xD8, 0x4F, 0x12, 0xC6, 0x37, 0x80, 0xA2, 0x09, 0xC1,
|
||||||
|
0xC8, 0x2B, 0x4B, 0x88, 0xAF, 0x34, 0x01, 0x38, 0x92, 0x74, 0x02, 0x8C, 0x6B, 0x53, 0x0B, 0x38,
|
||||||
|
0x8C, 0xD4, 0x47, 0x09, 0x0E, 0x4A, 0x9F, 0xDA, 0xFE, 0x94, 0x54, 0xDC, 0x03, 0x7B, 0xC0, 0x3E,
|
||||||
|
0xD0, 0x4B, 0x2D, 0x8A, 0xB5, 0xA8, 0xB2, 0x17, 0xA0, 0xA8, 0x24, 0x41, 0xD2, 0x0D, 0xF0, 0x08,
|
||||||
|
0xAC, 0xC7, 0x0A, 0x53, 0x4F, 0xD0, 0xAB, 0xEB, 0x8D, 0xED, 0x77, 0x12, 0x4F, 0x91, 0x4A, 0xB0,
|
||||||
|
0x25, 0x69, 0x90, 0x5B, 0x7E, 0x77, 0x66, 0x49, 0xD0, 0x01, 0x4E, 0x25, 0xB5, 0x25, 0xAD, 0x01,
|
||||||
|
0x67, 0xC0, 0x66, 0x52, 0xE5, 0x8C, 0xC4, 0xAE, 0xEE, 0x3B, 0x3F, 0xA9, 0x69, 0x46, 0xFF, 0xB5,
|
||||||
|
0x4C, 0x0B, 0x7E, 0x47, 0x5F, 0x1C, 0x07, 0x98, 0x0A, 0x67, 0x72, 0x39, 0x70, 0x0A, 0x46, 0x95,
|
||||||
|
0x35, 0x66, 0xAF, 0xC0, 0xB5, 0xED, 0xCF, 0x21, 0xBC, 0xFF, 0xA1, 0x1F, 0xB3, 0x6F, 0x68, 0x56,
|
||||||
|
0xDC, 0x8C, 0xF8, 0xB1, 0x07, 0x79, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42,
|
||||||
|
0x60, 0x82
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
const wxBitmap &icon_floppy_disk_24x24() // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
static wxBitmap bmp;
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(bitmapData.data(), bitmapData.size());
|
||||||
|
bmp = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
40
src/resources/icons_24x24/icon_minus_24x24.png.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto bitmapData = std::to_array<uint8_t>({
|
||||||
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D,
|
||||||
|
0xF8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||||
|
0x8D, 0x01, 0xC6, 0x66, 0xF7, 0xA4, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,
|
||||||
|
0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,
|
||||||
|
0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x00, 0x69, 0x49,
|
||||||
|
0x44, 0x41, 0x54, 0x48, 0x89, 0xED, 0x94, 0x31, 0x0A, 0x80, 0x40, 0x0C, 0x04, 0x27, 0xE2, 0x13,
|
||||||
|
0xEC, 0x7D, 0x88, 0xFE, 0xFF, 0x1F, 0xDA, 0xEA, 0x13, 0x94, 0xB5, 0x52, 0xBC, 0x14, 0x16, 0x77,
|
||||||
|
0x5E, 0x71, 0x90, 0x85, 0x34, 0x21, 0xD9, 0x81, 0x25, 0xC4, 0x24, 0x51, 0x53, 0x5D, 0x55, 0xF7,
|
||||||
|
0x00, 0x04, 0x20, 0x00, 0x00, 0xF4, 0xBE, 0x61, 0x66, 0x23, 0x30, 0x64, 0xFA, 0xED, 0x92, 0x96,
|
||||||
|
0xA4, 0x23, 0xE9, 0x29, 0x60, 0x06, 0x4E, 0x40, 0x99, 0x75, 0x00, 0xD3, 0xDB, 0xD3, 0x47, 0x74,
|
||||||
|
0x0F, 0x96, 0x28, 0xD9, 0x37, 0xFF, 0xEC, 0x0A, 0x23, 0xDA, 0x24, 0xAD, 0x9F, 0x80, 0xBF, 0xD5,
|
||||||
|
0xFE, 0x99, 0x06, 0x20, 0x00, 0x0D, 0x00, 0x2E, 0xA4, 0x0D, 0x38, 0x72, 0xA4, 0xA0, 0x2F, 0x0D,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
const wxBitmap &icon_minus_24x24() // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
static wxBitmap bmp;
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(bitmapData.data(), bitmapData.size());
|
||||||
|
bmp = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
46
src/resources/icons_24x24/icon_plus_24x24.png.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto bitmapData = std::to_array<uint8_t>({
|
||||||
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D,
|
||||||
|
0xF8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||||
|
0x8D, 0x01, 0xC6, 0x66, 0xF7, 0xA4, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,
|
||||||
|
0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,
|
||||||
|
0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x00, 0xC0, 0x49,
|
||||||
|
0x44, 0x41, 0x54, 0x48, 0x89, 0xE5, 0x94, 0x31, 0x0E, 0x82, 0x50, 0x10, 0x44, 0xDF, 0xA8, 0x47,
|
||||||
|
0xD0, 0x56, 0x6A, 0xCF, 0x00, 0x57, 0xF1, 0x74, 0x9E, 0xC5, 0xD2, 0x9A, 0x1A, 0x5B, 0x3C, 0x02,
|
||||||
|
0x66, 0x2C, 0x80, 0x04, 0x3E, 0x04, 0x01, 0x43, 0x02, 0x71, 0x92, 0x9F, 0x6C, 0x36, 0xBB, 0x7F,
|
||||||
|
0xB2, 0x3B, 0xF3, 0xBF, 0x6C, 0xB3, 0x24, 0x76, 0x8B, 0xDE, 0xBE, 0x3A, 0x02, 0x49, 0x89, 0xA4,
|
||||||
|
0x64, 0x12, 0x83, 0xED, 0x51, 0x07, 0x88, 0x80, 0x37, 0x50, 0x00, 0xE7, 0xB1, 0x7D, 0x53, 0x26,
|
||||||
|
0x38, 0x52, 0x4E, 0xBC, 0x07, 0x4E, 0x63, 0x9B, 0xD6, 0xA5, 0xC1, 0x7F, 0x12, 0x1C, 0xC2, 0x84,
|
||||||
|
0xA4, 0x88, 0x52, 0xD0, 0x10, 0x97, 0x66, 0x2C, 0xA9, 0xEF, 0xBE, 0x97, 0xED, 0xAC, 0x95, 0x09,
|
||||||
|
0xAC, 0x98, 0x50, 0x5A, 0xD1, 0x33, 0x4F, 0x01, 0xC4, 0x43, 0x36, 0xAD, 0x0B, 0x7F, 0x41, 0xAB,
|
||||||
|
0x5F, 0xE1, 0x67, 0xF7, 0x65, 0x45, 0xB7, 0x2A, 0xBE, 0x02, 0x69, 0x4F, 0x4D, 0x6E, 0xFB, 0xD9,
|
||||||
|
0x4C, 0x74, 0x34, 0xA8, 0x76, 0x98, 0x85, 0xF9, 0x60, 0xE7, 0xA9, 0xED, 0x47, 0x0F, 0x41, 0x07,
|
||||||
|
0xDB, 0xB7, 0xE9, 0xF6, 0x09, 0x3A, 0x22, 0x0F, 0x20, 0xA7, 0x7C, 0x23, 0x75, 0x3C, 0x0A, 0x1D,
|
||||||
|
0x9B, 0x0E, 0x16, 0x4B, 0x31, 0x80, 0xED, 0xFB, 0x22, 0x04, 0x73, 0xB0, 0x7D, 0x91, 0x3F, 0x39,
|
||||||
|
0x76, 0x8C, 0xA1, 0xD6, 0x20, 0xBA, 0x72, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
|
||||||
|
0x42, 0x60, 0x82
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
const wxBitmap &icon_plus_24x24() // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
static wxBitmap bmp;
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(bitmapData.data(), bitmapData.size());
|
||||||
|
bmp = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
60
src/resources/icons_24x24/icon_rotate-left_24x24.png.cpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto bitmapData = std::to_array<uint8_t>({
|
||||||
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D,
|
||||||
|
0xF8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||||
|
0x8D, 0x01, 0xC6, 0x66, 0xF7, 0xA4, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,
|
||||||
|
0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,
|
||||||
|
0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x01, 0x9E, 0x49,
|
||||||
|
0x44, 0x41, 0x54, 0x48, 0x89, 0xB5, 0xD6, 0xBF, 0x6A, 0x54, 0x41, 0x14, 0x06, 0xF0, 0xDF, 0xB8,
|
||||||
|
0xD9, 0x8D, 0x06, 0x4B, 0x0B, 0x0D, 0x89, 0x8F, 0x60, 0xAD, 0x16, 0x22, 0x82, 0xE9, 0x7D, 0x0D,
|
||||||
|
0x31, 0x31, 0x85, 0x16, 0x56, 0xB6, 0x62, 0x65, 0xA1, 0x82, 0x2F, 0xE1, 0x03, 0xF8, 0x27, 0x62,
|
||||||
|
0x11, 0x21, 0xB0, 0xC1, 0x42, 0xC4, 0xC2, 0xC2, 0x42, 0x49, 0x7C, 0x01, 0x5D, 0x30, 0x84, 0xB1,
|
||||||
|
0xD8, 0x73, 0x37, 0xC3, 0x35, 0xF7, 0xAE, 0x57, 0x77, 0x0F, 0x1C, 0x98, 0x39, 0xF7, 0x9C, 0xF3,
|
||||||
|
0xCD, 0x99, 0x39, 0xF3, 0xCD, 0x4D, 0x39, 0x67, 0xF3, 0x94, 0x13, 0x73, 0xCD, 0x8E, 0x85, 0x6A,
|
||||||
|
0x90, 0x52, 0xBA, 0x80, 0x1B, 0x78, 0x98, 0x73, 0x1E, 0x15, 0xF6, 0x3E, 0x2E, 0xE2, 0x32, 0xCE,
|
||||||
|
0xE0, 0x24, 0xBE, 0x60, 0x88, 0xED, 0x9C, 0xF3, 0x61, 0x2B, 0x42, 0xCE, 0x59, 0x6C, 0xD3, 0x53,
|
||||||
|
0x64, 0xBC, 0xC4, 0x29, 0xF4, 0x71, 0x17, 0xDF, 0xC2, 0x7E, 0x9C, 0xEE, 0x63, 0x13, 0x83, 0x2A,
|
||||||
|
0x4F, 0x5D, 0x4B, 0x80, 0x67, 0x45, 0xE0, 0x16, 0x76, 0x6B, 0xC9, 0x7E, 0xE2, 0x23, 0x3E, 0xC4,
|
||||||
|
0xB8, 0xFC, 0xF6, 0x1E, 0xE7, 0xBB, 0x00, 0x94, 0xFA, 0x02, 0xD7, 0xB1, 0x50, 0xF8, 0xF6, 0xC2,
|
||||||
|
0xF6, 0xAA, 0xF0, 0xDB, 0xC3, 0x6A, 0x57, 0x80, 0xCF, 0x58, 0x6A, 0x2A, 0x3F, 0xE2, 0x6E, 0xE2,
|
||||||
|
0x20, 0xFC, 0x77, 0xB1, 0xD8, 0xB5, 0x82, 0xB7, 0x38, 0x3D, 0x05, 0xE4, 0x56, 0xE1, 0xBF, 0xD1,
|
||||||
|
0x04, 0xF0, 0xA4, 0xE5, 0x30, 0xEF, 0xB7, 0x01, 0x44, 0xFC, 0xEB, 0xE2, 0xE0, 0x7B, 0x95, 0x7D,
|
||||||
|
0xD2, 0xA6, 0x78, 0x1C, 0x0E, 0xFD, 0x5A, 0xA3, 0x1D, 0xE2, 0xB9, 0xE9, 0xF2, 0x00, 0xD7, 0x70,
|
||||||
|
0xD6, 0xB8, 0xAD, 0xB7, 0xA1, 0x75, 0x55, 0x5D, 0x14, 0x03, 0xFC, 0x88, 0x45, 0xDE, 0xA9, 0xEC,
|
||||||
|
0x33, 0xBB, 0xC9, 0x39, 0xE7, 0x5F, 0xF8, 0x1A, 0xD3, 0x73, 0x95, 0x7D, 0xD6, 0x54, 0x91, 0x2A,
|
||||||
|
0xBC, 0x99, 0x03, 0xA4, 0x94, 0x06, 0x58, 0x89, 0xE9, 0xF7, 0x99, 0x03, 0xE0, 0x2A, 0x96, 0x62,
|
||||||
|
0xFC, 0x6E, 0x62, 0xAD, 0x1D, 0xD4, 0x22, 0x1E, 0xE1, 0xDE, 0x3F, 0x1C, 0xF2, 0x96, 0xA3, 0x1B,
|
||||||
|
0x3D, 0x69, 0xD3, 0xBA, 0xD3, 0x15, 0x47, 0xBD, 0xBF, 0xD9, 0x21, 0xF9, 0xBA, 0x69, 0x17, 0xAD,
|
||||||
|
0xA8, 0x60, 0x27, 0x1C, 0x0F, 0xB0, 0xFE, 0x97, 0xC9, 0x2B, 0xAA, 0x18, 0x6A, 0xA2, 0x8A, 0x22,
|
||||||
|
0x60, 0x25, 0xCA, 0x2C, 0x99, 0x75, 0x4D, 0x41, 0xC9, 0xC6, 0x3D, 0xBF, 0x86, 0x37, 0x85, 0x5F,
|
||||||
|
0x3B, 0xD9, 0xD5, 0x40, 0x56, 0xFD, 0x49, 0xD7, 0x23, 0x7C, 0x0A, 0x1D, 0xD5, 0xBE, 0x0D, 0x8F,
|
||||||
|
0x4B, 0xDE, 0x08, 0x50, 0x6C, 0xD7, 0x6D, 0x63, 0x6E, 0x69, 0x7B, 0x70, 0x36, 0xB4, 0x3C, 0x38,
|
||||||
|
0x29, 0x92, 0x35, 0x4A, 0x4A, 0xA9, 0x67, 0xCC, 0x2D, 0x97, 0xB0, 0x1C, 0xE6, 0xBD, 0x68, 0xC5,
|
||||||
|
0x9D, 0x3C, 0xE5, 0xC9, 0x9C, 0x0A, 0xF0, 0xBF, 0x32, 0xF7, 0xBF, 0x8A, 0xDF, 0xAD, 0x7A, 0x6F,
|
||||||
|
0xB6, 0xEA, 0x73, 0xB1, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60,
|
||||||
|
0x82
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
const wxBitmap &icon_rotate_left_24x24() // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
static wxBitmap bmp;
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(bitmapData.data(), bitmapData.size());
|
||||||
|
bmp = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
48
src/resources/icons_24x24/icon_xmark_24x24.png.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto bitmapData = std::to_array<uint8_t>({
|
||||||
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D,
|
||||||
|
0xF8, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||||
|
0x8D, 0x01, 0xC6, 0x66, 0xF7, 0xA4, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,
|
||||||
|
0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,
|
||||||
|
0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x00, 0xE4, 0x49,
|
||||||
|
0x44, 0x41, 0x54, 0x48, 0x89, 0xDD, 0x95, 0x49, 0x0E, 0xC2, 0x30, 0x0C, 0x45, 0x9F, 0x7B, 0x83,
|
||||||
|
0x42, 0x19, 0xCE, 0x02, 0x2B, 0x86, 0xFB, 0x1F, 0x04, 0x16, 0x4C, 0x2D, 0x5B, 0xB3, 0x68, 0x2A,
|
||||||
|
0x3A, 0x38, 0x69, 0x82, 0x40, 0x48, 0xB5, 0x94, 0x95, 0x7F, 0xFE, 0x4B, 0xED, 0xC4, 0x15, 0x55,
|
||||||
|
0xE5, 0x97, 0x91, 0xFD, 0xD4, 0x7D, 0xBA, 0x00, 0x11, 0x29, 0x44, 0x64, 0x11, 0x6B, 0xE2, 0xF4,
|
||||||
|
0x4B, 0x33, 0xA9, 0xAA, 0x9D, 0x05, 0xCC, 0x80, 0x0B, 0xF0, 0x00, 0x76, 0xFD, 0xBC, 0xA1, 0xDF,
|
||||||
|
0x02, 0x37, 0xE0, 0x0E, 0xAC, 0x07, 0x79, 0x0F, 0xE0, 0x0A, 0x28, 0x50, 0x85, 0x20, 0xCE, 0xFC,
|
||||||
|
0xEE, 0xB4, 0x25, 0xB0, 0x1A, 0x05, 0xB8, 0x8D, 0x9B, 0xD6, 0xC6, 0x0A, 0x38, 0x44, 0x68, 0xF6,
|
||||||
|
0xA6, 0x57, 0xE0, 0x74, 0x5E, 0x48, 0xAC, 0x79, 0x10, 0xD0, 0x32, 0xBA, 0xB5, 0x21, 0x29, 0xE6,
|
||||||
|
0xA3, 0x00, 0xA3, 0xCE, 0x55, 0x8A, 0x79, 0x14, 0xC0, 0xF8, 0x12, 0x6F, 0x5F, 0xAC, 0x95, 0xF2,
|
||||||
|
0xD0, 0x24, 0x41, 0xFB, 0x8E, 0xBF, 0x96, 0xC8, 0x68, 0x68, 0xD3, 0xE4, 0x76, 0xE3, 0x8F, 0x1F,
|
||||||
|
0x01, 0x0C, 0xA3, 0xFE, 0x35, 0x6D, 0x72, 0xCF, 0x10, 0x24, 0xD9, 0x3C, 0x15, 0x62, 0x99, 0xE7,
|
||||||
|
0x74, 0x47, 0x85, 0xFF, 0x11, 0x75, 0x4B, 0x58, 0x12, 0x39, 0x8B, 0x72, 0xEA, 0x61, 0x57, 0x46,
|
||||||
|
0x35, 0xB1, 0x86, 0x5C, 0xF1, 0x0C, 0x3B, 0x71, 0xA2, 0x4E, 0x88, 0x48, 0x01, 0x64, 0xAA, 0x7A,
|
||||||
|
0x8A, 0xB9, 0x89, 0x22, 0x32, 0x77, 0xFA, 0xF3, 0x20, 0x67, 0x01, 0xBE, 0x19, 0x13, 0xFD, 0x65,
|
||||||
|
0x7E, 0x33, 0x5E, 0x9B, 0xE0, 0xA4, 0x87, 0x67, 0x34, 0xD0, 0xD5, 0x00, 0x00, 0x00, 0x00, 0x49,
|
||||||
|
0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
|
||||||
|
});
|
||||||
|
// clang-format on
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
const wxBitmap &icon_xmark_24x24() // NOLINT(misc-use-internal-linkage)
|
||||||
|
{
|
||||||
|
static wxBitmap bmp;
|
||||||
|
if (!bmp.IsOk())
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(bitmapData.data(), bitmapData.size());
|
||||||
|
bmp = wxBitmap(wxImage(is, wxBITMAP_TYPE_ANY, -1), -1);
|
||||||
|
}
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
35
src/resources/resources.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <wx/animate.h>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/image.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
//--- icons_24x24 ----------------------------------------------
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
extern const wxBitmap &icon_floppy_disk_24x24();
|
||||||
|
extern const wxBitmap &icon_minus_24x24();
|
||||||
|
extern const wxBitmap &icon_plus_24x24();
|
||||||
|
extern const wxBitmap &icon_rotate_left_24x24();
|
||||||
|
extern const wxBitmap &icon_xmark_24x24();
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#define wxGetBitmapFromMemory(name) _wxGetBitmapFromMemory(name, sizeof(name))
|
||||||
|
inline wxBitmap _wxGetBitmapFromMemory(const unsigned char *data, int length)
|
||||||
|
{
|
||||||
|
wxMemoryInputStream is(data, length);
|
||||||
|
return { wxImage(is, wxBITMAP_TYPE_ANY, -1), -1 };
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#define wxGetAnimationFromMemory(name) _wxGetAnimationFromMemory(name, sizeof(name))
|
||||||
|
inline wxAnimation _wxGetAnimationFromMemory(const unsigned char *data, int length)
|
||||||
|
{
|
||||||
|
wxAnimation animate;
|
||||||
|
wxMemoryInputStream is(data, length);
|
||||||
|
animate.Load(is);
|
||||||
|
return animate;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
13
src/version/appVersion.conf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[buildInformation]
|
||||||
|
buildDate = 2022-10-14 09:36:16
|
||||||
|
buildNumber = 155
|
||||||
|
companyName = SD-Innovation S.A.S.
|
||||||
|
copyright = Copyright SDi (C) 2019
|
||||||
|
fileDecription = Aggregometer application manager
|
||||||
|
internalName = thromboSoft.exe
|
||||||
|
majorRevision = 0
|
||||||
|
majorVersion = 2
|
||||||
|
minorRevision = 0
|
||||||
|
minorVersion = 3
|
||||||
|
originalFilename = thromboSoft.exe
|
||||||
|
productName = ThromboSoft
|
||||||
13
src/version/appVersion.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace version
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
static constexpr std::string_view appName = "ThromboSoft";
|
||||||
|
static constexpr std::string_view fullVersion = "2.0.3.0 debugAndTests";
|
||||||
|
static constexpr std::string_view shortVersion = "2.0.3.0 debugAndTests";
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace version
|
||||||
19
src/version/appVersion.rc
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define COMPANY_NAME "SD-Innovation S.A.S."
|
||||||
|
#define PRODUCT_NAME "ThromboSoft"
|
||||||
|
#define FILE_DESCRIPTION "ThromboAggregometer application manager"
|
||||||
|
#define ORIGINAL_FILENAME "thromboSoft.exe"
|
||||||
|
#define INTERNAL_NAME "thromboSoft.exe"
|
||||||
|
#define COPYRIGHT "Copyright SDi (C) 2024"
|
||||||
|
#define FULL_VERSION "2.0.3.0"
|
||||||
|
#define LONG_VERSION "2.0.3.0"
|
||||||
|
#define SHORT_VERSION "2.0"
|
||||||
|
#define BUILD_DATE "2024-10-25 16:10:19"
|
||||||
|
#define BUILD_NUMBER 435
|
||||||
|
#define MAJOR_VERSION 2
|
||||||
|
#define MINOR_VERSION 0
|
||||||
|
#define MAJOR_REVISION 3
|
||||||
|
#define MINOR_REVISION 0
|
||||||
|
|
||||||
|
//#endif // APPVERSION_20221014093616_H
|
||||||