Compare commits

10 Commits

Author SHA1 Message Date
Sylvain Schneider
89f277d013 Work on the graphical interface requirements tree 2026-03-17 17:11:02 +01:00
Sylvain Schneider
806aeb4824 Adds resources and encoded resources 2026-03-17 17:10:28 +01:00
Sylvain Schneider
d48abfba79 GUI evolutions 2026-03-17 11:53:20 +01:00
Sylvain Schneider
03d2e94f8b Work on the graphical interface for presenting requirements details 2026-03-13 16:34:34 +01:00
Sylvain Schneider
25fc14d6bd Work on requirements management 2026-03-13 10:23:40 +01:00
Sylvain Schneider
16787ac642 Implementation of a dBus management class for the GUI 2026-03-12 18:57:35 +01:00
Sylvain Schneider
e64921702b Optimizing project management and its requirements 2026-03-12 18:56:55 +01:00
Sylvain Schneider
f25c5789ea Initial commit of source code 2026-03-12 16:32:03 +01:00
Sylvain Schneider
b5d0fef4d9 Adds the sdi_toolBox library (temporary version) 2026-03-12 16:31:18 +01:00
Sylvain Schneider
0601326e2b Adds the project's compilation files 2026-03-12 16:30:08 +01:00
102 changed files with 58596 additions and 0 deletions

215
CMakeLists.txt Normal file
View 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
View 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

Binary file not shown.

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,6 @@
#pragma once
/**
* @namespace sdi_ToolBox::comm
* @brief Communication protocols and binary handling.
*/

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}
//--------------------------------------------------------------

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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

View 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

View 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;
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,6 @@
#pragma once
/**
* @namespace sdi_ToolBox
* @brief Root namespace for SD-Innovation ToolBox utilities.
*/

View File

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

View 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);
}
//--------------------------------------------------------------

View 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

View 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));
}
}
//--------------------------------------------------------------

View 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

View 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;
}
//--------------------------------------------------------------

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

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

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

View File

@@ -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));
}
}
//--------------------------------------------------------------

View File

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

View File

@@ -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();
}
//--------------------------------------------------------------

View File

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

View File

@@ -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 {};
}
//--------------------------------------------------------------

View File

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

View 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)
{
}
//--------------------------------------------------------------

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

Binary file not shown.

50
src/kwaFr_app.cpp Normal file
View 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
View 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
View 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;
}
//--------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View 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_

View 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();
}
//--------------------------------------------------------------

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
src/resources/bin2c.exe Normal file

Binary file not shown.

Binary file not shown.

View 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;
}
//--------------------------------------------------------------

View 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;
}
//--------------------------------------------------------------

View 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;
}
//--------------------------------------------------------------

View 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;
}
//--------------------------------------------------------------

View 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
View 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;
}
//--------------------------------------------------------------

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

BIN
src/version/autoVersion.exe Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More