diff --git a/.gitignore b/.gitignore index 06fddcf..15177f6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ debian/x0-db-install debian/x0-db-install-tpl debian/x0-msg-server debian/.debhelper +_codeql_detected_source_root +cpp/build/ diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..ee5471a --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,90 @@ +#-------1---------2---------3---------4---------5---------6---------7--------# +#- Copyright WEB/codeX, clickIT 2011 - 2025 -# +#-------1---------2---------3---------4---------5---------6---------7--------# +#- -# +#-------1---------2---------3---------4---------5---------6---------7--------# +#- x0 C++ Framework - CMake Build Configuration -# +#-------1---------2---------3---------4---------5---------6---------7--------# + +cmake_minimum_required(VERSION 3.16) +project(x0-cpp VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Find required packages +find_package(Qt6 COMPONENTS Core Widgets QUIET) +if(NOT Qt6_FOUND) + find_package(Qt5 5.15 COMPONENTS Core Widgets REQUIRED) + set(QT_LIBS Qt5::Core Qt5::Widgets) +else() + set(QT_LIBS Qt6::Core Qt6::Widgets) +endif() + +# nlohmann/json - Header-only library +# Can be installed via: apt-get install nlohmann-json3-dev +# Or fetched automatically +include(FetchContent) +FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.3 +) +FetchContent_MakeAvailable(json) + +# Include directories +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Library source files (without main.cpp) +set(X0_LIB_SOURCES + src/x0BaseElement.cpp + src/x0BaseObject.cpp + src/x0Factory.cpp + src/x0Screen.cpp + src/x0Reactor.cpp + src/x0ObjDiv.cpp + src/x0ObjButton.cpp +) + +# Header files +set(X0_HEADERS + include/x0BaseElement.h + include/x0BaseObject.h + include/x0Factory.h + include/x0Screen.h + include/x0Reactor.h + include/x0ObjDiv.h + include/x0ObjButton.h +) + +# Create static library +add_library(x0-lib STATIC ${X0_LIB_SOURCES} ${X0_HEADERS}) +target_link_libraries(x0-lib PUBLIC + ${QT_LIBS} + nlohmann_json::nlohmann_json +) + +# Create main executable +add_executable(x0-app src/main.cpp) +target_link_libraries(x0-app PRIVATE x0-lib) + +# Create test executable for validating examples +add_executable(x0-test-examples test/test_examples.cpp) +target_link_libraries(x0-test-examples PRIVATE + x0-lib + nlohmann_json::nlohmann_json +) + +# Enable testing +enable_testing() + +# Define examples directory path +set(EXAMPLES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../example") +add_test(NAME test_examples COMMAND x0-test-examples "${EXAMPLES_DIR}") + +# Install targets +install(TARGETS x0-app DESTINATION bin) +install(FILES ${X0_HEADERS} DESTINATION include/x0) diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..0f59777 --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,174 @@ +# x0 C++ Framework + +C++ variant of the x0 JavaScript framework using **Qt** for graphical rendering and **nlohmann::json** for configuration management. + +## Overview + +This directory contains a C++ implementation of the x0 framework, providing equivalent functionality to the JavaScript version in `/www/`. The C++ variant is designed for desktop applications requiring native performance and Qt-based graphical output. + +## Architecture + +The C++ framework mirrors the JavaScript architecture: + +| JavaScript (`/www/`) | C++ (`/cpp/`) | Description | +|---------------------------|------------------------|------------------------------------------------| +| `sysBaseDOMElement.js` | `x0BaseElement.h/cpp` | Base element wrapping Qt widgets | +| `sysBaseObject.js` | `x0BaseObject.h/cpp` | Hierarchical object with children | +| `sysFactory.js` | `x0Factory.h/cpp` | Object factory and screen management | +| `sysScreen.js` | `x0Screen.h/cpp` | Screen/view management | +| `sysReactor.js` | `x0Reactor.h/cpp` | Event system | +| `sysObjDiv.js` | `x0ObjDiv.h/cpp` | Container widget (QFrame) | +| `sysObjButton.js` | `x0ObjButton.h/cpp` | Button widget (QPushButton) | + +## Dependencies + +- **Qt 5.15+** or **Qt 6.x** (Core, Widgets modules) +- **nlohmann/json 3.x** (automatically fetched via CMake) +- **CMake 3.16+** + +## Building + +### Prerequisites + +```bash +# Ubuntu/Debian +sudo apt-get install cmake qt6-base-dev + +# Or for Qt5 +sudo apt-get install cmake qtbase5-dev +``` + +### Build Steps + +```bash +cd cpp +mkdir build && cd build +cmake .. +make -j$(nproc) +``` + +### Running + +```bash +./x0-app +``` + +## Usage Example + +```cpp +#include "x0Factory.h" +#include "x0Screen.h" +#include "x0ObjDiv.h" +#include "x0ObjButton.h" + +using namespace x0; + +int main() { + // Load configuration + json skeleton = loadFromFile("skeleton.json"); + json dataObject = loadFromFile("objects.json"); + + // Initialize factory + x0Factory& factory = x0Factory::instance(); + factory.setDataSkeleton(skeleton); + factory.setDataObject(dataObject); + factory.init(); + + // Access screens and objects + x0Screen* screen = factory.getScreenById("MainScreen"); + x0BaseObject* button = factory.getObjectById("MyButton"); + + return 0; +} +``` + +## JSON Configuration + +The C++ framework uses the same JSON configuration format as the JavaScript version: + +### Skeleton Configuration +```json +{ + "MainScreen": [ + {"MainDiv": {"RefID": "MainScreen"}}, + {"WelcomeText": {"RefID": "MainDiv"}}, + {"ActionButton": {"RefID": "MainDiv"}} + ] +} +``` + +### Object Configuration +```json +{ + "ActionButton": { + "Type": "Button", + "Attributes": { + "Style": "btn btn-primary", + "TextID": "BTN.SUBMIT", + "OnClick": "/api/submit", + "FireEvents": ["DataRefresh"] + } + } +} +``` + +## Class Hierarchy + +``` +QObject +└── x0BaseElement (Qt widget wrapper) + └── x0BaseObject (hierarchical object) + ├── x0ObjDiv (container) + └── x0ObjButton (button) + +x0Factory (singleton, object management) +x0Screen (screen/view container) +x0Reactor (event dispatching) +``` + +## Features + +- **Hierarchical Object Model**: Parent-child relationships with recursive rendering +- **JSON Configuration**: Load UI definitions from JSON files +- **Qt Integration**: Native desktop widgets with Qt +- **Event System**: Register and dispatch events across objects +- **Screen Management**: Multiple screens with switching support +- **Object Factory**: Dynamic object creation by type + +## Extending + +To add new widget types: + +1. Create header in `include/`: +```cpp +// x0ObjMyWidget.h +class x0ObjMyWidget : public x0BaseObject { + Q_OBJECT +public: + void createWidget(const QString& objectId) override; + void init() override; +}; +``` + +2. Create implementation in `src/`: +```cpp +// x0ObjMyWidget.cpp +void x0ObjMyWidget::createWidget(const QString& objectId) { + m_widget = std::make_unique(); + m_widget->setObjectName(objectId); +} +``` + +3. Register in factory: +```cpp +x0Factory::instance().registerObjectType("MyWidget", + []() { return std::make_shared(); }); +``` + +## License + +AGPL-3.0 - See [../LICENSE](../LICENSE) + +## Contributing + +See [../CONTRIBUTING.md](../CONTRIBUTING.md) diff --git a/cpp/include/x0BaseElement.h b/cpp/include/x0BaseElement.h new file mode 100644 index 0000000..b49a33f --- /dev/null +++ b/cpp/include/x0BaseElement.h @@ -0,0 +1,109 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Base Element (Qt Widget Wrapper) -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysBaseDOMElement.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_BASE_ELEMENT_H +#define X0_BASE_ELEMENT_H + +#include +#include +#include +#include +#include + +namespace x0 { + +using json = nlohmann::json; + +/** + * @brief Base element class wrapping Qt widgets + * + * This class provides the foundation for all UI elements in the x0 C++ framework. + * It manages the underlying Qt widget and provides methods for styling, + * visibility control, and event handling - equivalent to sysBaseDOMElement.js. + */ +class x0BaseElement : public QObject { + Q_OBJECT + +public: + explicit x0BaseElement(QObject* parent = nullptr); + virtual ~x0BaseElement(); + + // Widget management + virtual void createWidget(const QString& objectId); + virtual QWidget* getWidget() const; + void setParentWidget(QWidget* parent); + + // Style management + void setStyleClass(const QString& styleClass); + void addStyleClass(const QString& styleClass); + void removeStyleClass(const QString& styleClass); + bool hasStyleClass(const QString& styleClass) const; + QString getStyleClasses() const; + + // Value management + void setValue(const QString& value); + QString getValue() const; + + // Visibility + void setVisibleState(const QString& state); + QString getVisibleState() const; + void switchVisibleState(); + + // Enable/Disable + void enable(); + void disable(); + bool isEnabled() const; + + // Attributes + void setAttribute(const QString& attribute, const QString& value); + QString getAttribute(const QString& attribute) const; + void setAttributes(const QMap& attributes); + + // Style properties + void setStyleTop(int top); + void setStyleLeft(int left); + void setStyleWidth(int width); + void setStyleHeight(int height); + void setStyleZIndex(int zIndex); + void applyStyleAttributes(); + + // Object identification + QString getObjectId() const { return m_objectId; } + void setObjectId(const QString& id) { m_objectId = id; } + QString getParentId() const { return m_parentId; } + void setParentId(const QString& id) { m_parentId = id; } + +protected: + std::unique_ptr m_widget; + QString m_objectId; + QString m_parentId; + QString m_value; + QString m_styleClass; + QString m_visibleState; + QMap m_attributes; + + // Style positioning + int m_styleTop = -1; + int m_styleLeft = -1; + int m_styleWidth = -1; + int m_styleHeight = -1; + int m_styleZIndex = -1; + + QStringList m_styleClasses; + +signals: + void visibilityChanged(bool visible); + void styleChanged(); + void valueChanged(const QString& value); +}; + +} // namespace x0 + +#endif // X0_BASE_ELEMENT_H diff --git a/cpp/include/x0BaseObject.h b/cpp/include/x0BaseObject.h new file mode 100644 index 0000000..ecbe869 --- /dev/null +++ b/cpp/include/x0BaseObject.h @@ -0,0 +1,103 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Base Object (Hierarchical Object Model) -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysBaseObject.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_BASE_OBJECT_H +#define X0_BASE_OBJECT_H + +#include "x0BaseElement.h" +#include +#include +#include + +namespace x0 { + +/** + * @brief Base object class with hierarchical child management + * + * This class extends x0BaseElement to provide hierarchical object management, + * supporting parent-child relationships, recursive rendering, and event + * propagation - equivalent to sysBaseObject.js. + */ +class x0BaseObject : public x0BaseElement { + Q_OBJECT + +public: + explicit x0BaseObject(QObject* parent = nullptr); + virtual ~x0BaseObject(); + + // Child object management + void addObject(std::shared_ptr childObject); + void removeObject(int index); + void removeObject(x0BaseObject* child); + std::shared_ptr getChildByIndex(int index) const; + int getChildIndexById(const QString& id) const; + int getChildCount() const; + const std::vector>& getChildObjects() const; + + // Object lookup + x0BaseObject* getObjectById(const QString& objectId); + std::vector getObjectsByType(const QString& objectType); + std::vector getObjectsByAttribute(const QString& attribute); + std::map getAllObjects(); + + // Parent management + void setParentObject(x0BaseObject* parent); + x0BaseObject* getParentObject() const; + + // Object type and configuration + void setObjectType(const QString& type) { m_objectType = type; } + QString getObjectType() const { return m_objectType; } + void setJsonConfig(const json& config) { m_jsonConfig = config; } + const json& getJsonConfig() const { return m_jsonConfig; } + + // Rendering + virtual void renderObject(const QString& prefix = QString()); + virtual void init(); + virtual void reset(); + virtual void updateValue(); + + // State management + void setActivated(); + void setDeactivated(); + bool isDeactivated() const { return m_deactivated; } + + // Data management + virtual json getObjectData(); + virtual void setObjectData(const json& data); + virtual void appendObjectData(const json& data); + + // Recursive processing + void processReset(); + void processUpdate(); + void processEventListeners(); + + // Event listeners + using EventCallback = std::function; + void addEventListener(const QString& type, EventCallback callback); + void removeEventListener(const QString& type); + +protected: + std::vector> m_childObjects; + x0BaseObject* m_parentObject = nullptr; + QString m_objectType; + json m_jsonConfig; + bool m_deactivated = false; + bool m_overrideDomObjectId = false; + int m_hierarchyLevel = 0; + + std::map m_eventListeners; + + // Helper for recursive object collection + void collectObjects(std::map& items); +}; + +} // namespace x0 + +#endif // X0_BASE_OBJECT_H diff --git a/cpp/include/x0Factory.h b/cpp/include/x0Factory.h new file mode 100644 index 0000000..df0443e --- /dev/null +++ b/cpp/include/x0Factory.h @@ -0,0 +1,155 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Object Factory -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysFactory.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_FACTORY_H +#define X0_FACTORY_H + +#include "x0Screen.h" +#include "x0Reactor.h" +#include +#include +#include +#include +#include + +namespace x0 { + +// Forward declarations +class x0BaseObject; + +/** + * @brief Factory class for creating and managing x0 objects + * + * This singleton class serves as the central hub for object creation, + * screen management, and global state management. Equivalent to + * sysFactory.js in the JavaScript version. + */ +class x0Factory : public QObject { + Q_OBJECT + +public: + // Singleton access + static x0Factory& instance(); + + // Prevent copying + x0Factory(const x0Factory&) = delete; + x0Factory& operator=(const x0Factory&) = delete; + + // Initialization + void init(); + + // Object creation registry + using ObjectCreator = std::function()>; + void registerObjectType(const QString& type, ObjectCreator creator); + std::shared_ptr createObject(const QString& type); + + // Screen management + x0Screen* addScreen(const QString& screenId, const json& skeletonData); + x0Screen* getScreenById(const QString& screenId); + QMap>& getScreens(); + x0Screen* getLastScreenObject(); + + // Screen switching + void switchScreen(const QString& screenId); + void switchScreensToBackground(); + void switchScreenToForeground(x0Screen* screen); + void triggerScreenDataLoad(const QString& screenId); + QString getCurrentScreenId() const { return m_currentScreenId; } + + // Object lookup + x0BaseObject* getObjectById(const QString& objectId); + std::map> getObjectsByAttribute(const QString& attribute); + std::vector getObjectsByType(const QString& screenId, const QString& type); + + // Global data + void setDataSkeleton(const json& data) { m_dataSkeleton = data; } + const json& getDataSkeleton() const { return m_dataSkeleton; } + void setDataObject(const json& data) { m_dataObject = data; } + const json& getDataObject() const { return m_dataObject; } + void setDataMenu(const json& data) { m_dataMenu = data; } + const json& getDataMenu() const { return m_dataMenu; } + + // Screen configuration + void setScreenConfig(const json& config) { m_screenConfig = config; } + const json& getScreenConfig() const { return m_screenConfig; } + void setDefaultScreen(const QString& screenId) { m_displayDefaultScreen = screenId; } + QString getDefaultScreen() const { return m_displayDefaultScreen; } + + // Styling + void setDefaultStyleScreen(const QString& style) { m_defaultStyleScreen = style; } + QString getDefaultStyleScreen() const { return m_defaultStyleScreen; } + void setDefaultStyleMenu(const QString& style) { m_defaultStyleMenu = style; } + QString getDefaultStyleMenu() const { return m_defaultStyleMenu; } + + // Global variables + json getGlobalVar(const QString& key) const; + void setGlobalData(const json& data) { m_globalData = data; } + + // Text/localization + QString getText(const QString& textId) const; + void setTextData(const json& data) { m_textData = data; } + void setUserLanguage(const QString& lang) { m_userLanguage = lang; } + QString getUserLanguage() const { return m_userLanguage; } + + // Reactor (event system) + x0Reactor* getReactor() { return m_reactor.get(); } + + // User functions + void registerUserFunction(const QString& functionId, std::function func); + std::function getUserFunction(const QString& functionId); + + // Error container reset + void resetErrorContainer(); + + // Object hierarchy setup helper + void setupObjectRefsRecursive(const json& objDefs, x0BaseObject* refObj); + +signals: + void initSystemComplete(); + void screenSwitched(const QString& screenId); + +private: + x0Factory(QObject* parent = nullptr); + ~x0Factory(); + + // Screen storage + QMap> m_screens; + QString m_currentScreenId; + QString m_displayDefaultScreen; + + // Data storage + json m_dataSkeleton; + json m_dataObject; + json m_dataMenu; + json m_screenConfig; + json m_globalData; + json m_textData; + + // Styling + QString m_defaultStyleScreen; + QString m_defaultStyleMenu; + QString m_userLanguage; + + // Object creation registry + QMap m_objectCreators; + + // Event reactor + std::unique_ptr m_reactor; + + // User functions + QMap> m_userFunctions; + + // Overlay reference count + int m_overlayRefCount = 0; +}; + +} // namespace x0 + +#endif // X0_FACTORY_H diff --git a/cpp/include/x0ObjButton.h b/cpp/include/x0ObjButton.h new file mode 100644 index 0000000..035f0ba --- /dev/null +++ b/cpp/include/x0ObjButton.h @@ -0,0 +1,78 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Button Widget -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysObjButton.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_OBJ_BUTTON_H +#define X0_OBJ_BUTTON_H + +#include "x0BaseObject.h" +#include + +namespace x0 { + +/** + * @brief Button widget class + * + * This class represents a clickable button widget. It's the Qt equivalent + * of the JavaScript sysObjButton.js class and handles click events, + * form validation, and service calls. + */ +class x0ObjButton : public x0BaseObject { + Q_OBJECT + +public: + explicit x0ObjButton(QObject* parent = nullptr); + virtual ~x0ObjButton(); + + // Override base methods + void createWidget(const QString& objectId) override; + void init() override; + + // Button-specific functionality + void setButtonText(const QString& text); + QString getButtonText() const; + + void setDisabled(bool disabled); + bool isDisabled() const { return m_disabled; } + + // Form validation + void validateForm(); + bool getValidateResultError() const { return m_validateResultError; } + + // Service calls + void callService(); + void setCallUrl(const QString& url) { m_callUrl = url; } + QString getCallUrl() const { return m_callUrl; } + + // Get the underlying QPushButton + QPushButton* getButton() const; + +signals: + void clicked(); + void serviceCallComplete(const json& result); + void validationComplete(bool success); + +public slots: + void onButtonClick(); + +protected: + void processActions(); + void addNotifyHandler(); + +private: + QString m_callUrl; + bool m_disabled = false; + bool m_validateResultError = true; + bool m_formValidate = false; + bool m_callService = false; +}; + +} // namespace x0 + +#endif // X0_OBJ_BUTTON_H diff --git a/cpp/include/x0ObjDiv.h b/cpp/include/x0ObjDiv.h new file mode 100644 index 0000000..cf95b81 --- /dev/null +++ b/cpp/include/x0ObjDiv.h @@ -0,0 +1,51 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Div Container Widget -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysObjDiv.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_OBJ_DIV_H +#define X0_OBJ_DIV_H + +#include "x0BaseObject.h" +#include + +namespace x0 { + +/** + * @brief Container widget class (equivalent to HTML div) + * + * This class represents a container widget that can hold other widgets. + * It's the Qt equivalent of the JavaScript sysObjDiv.js class and serves + * as the basic building block for UI layouts. + */ +class x0ObjDiv : public x0BaseObject { + Q_OBJECT + +public: + explicit x0ObjDiv(QObject* parent = nullptr); + virtual ~x0ObjDiv(); + + // Override base methods + void createWidget(const QString& objectId) override; + void init() override; + void reset() override; + + // Div-specific functionality + void setDomType(const QString& type); + QString getDomType() const { return m_domType; } + + // Get the underlying QFrame + QFrame* getFrame() const; + +protected: + QString m_domType = "div"; +}; + +} // namespace x0 + +#endif // X0_OBJ_DIV_H diff --git a/cpp/include/x0Reactor.h b/cpp/include/x0Reactor.h new file mode 100644 index 0000000..8a606ab --- /dev/null +++ b/cpp/include/x0Reactor.h @@ -0,0 +1,103 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Event Reactor -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysReactor.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_REACTOR_H +#define X0_REACTOR_H + +#include +#include +#include +#include +#include + +namespace x0 { + +using json = nlohmann::json; + +// Forward declaration +class x0BaseObject; + +/** + * @brief Event structure for reactor system + */ +struct x0Event { + QString id; + x0BaseObject* objectRef; + QString type; + json attributes; + + x0Event(const QString& eventId, x0BaseObject* obj, const QString& eventType, const json& attrs = json()) + : id(eventId), objectRef(obj), type(eventType), attributes(attrs) {} +}; + +/** + * @brief Reactor class for event dispatching and handling + * + * This class manages event registration, dispatching, and handling + * across the x0 framework. Equivalent to sysReactor.js. + */ +class x0Reactor : public QObject { + Q_OBJECT + +public: + explicit x0Reactor(QObject* parent = nullptr); + virtual ~x0Reactor(); + + // Event registration + void registerEvent(const json& attributes, x0BaseObject* processObject, const QString& type); + void unregisterEvent(const QString& eventId, x0BaseObject* object); + + // Event dispatching + void dispatchEvent(const QString& eventId); + void fireEvents(const QStringList& events); + void fireEvents(const json& events); + + // Event query + QVector getEventsByType(const QString& type) const; + QVector getEventsForObject(x0BaseObject* object) const; + + // Clear events + void clearEvents(); + void clearEventsForObject(x0BaseObject* object); + +signals: + void eventDispatched(const QString& eventId); + void eventRegistered(const QString& eventId, const QString& type); + +private: + QVector m_events; +}; + +/** + * @brief Helper class for setting object property values via events + * + * This class handles the "SetObjectPropertyValues" event type, + * allowing dynamic property updates through the event system. + */ +class SetObjectPropertyValues : public QObject { + Q_OBJECT + +public: + explicit SetObjectPropertyValues(const x0Event& event, QObject* parent = nullptr); + void callService(); + +signals: + void serviceCallComplete(const json& result); + +private slots: + void handleServiceResponse(const json& response); + +private: + x0Event m_event; +}; + +} // namespace x0 + +#endif // X0_REACTOR_H diff --git a/cpp/include/x0Screen.h b/cpp/include/x0Screen.h new file mode 100644 index 0000000..dbaf6c6 --- /dev/null +++ b/cpp/include/x0Screen.h @@ -0,0 +1,92 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Screen Management -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Equivalent to JavaScript: sysScreen.js -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#ifndef X0_SCREEN_H +#define X0_SCREEN_H + +#include "x0BaseObject.h" +#include "x0ObjDiv.h" +#include +#include + +namespace x0 { + +/** + * @brief Screen class managing a hierarchical view of objects + * + * This class represents a screen/view in the application, containing + * a hierarchy of objects that can be rendered and managed together. + * Equivalent to sysScreen.js in the JavaScript version. + */ +class x0Screen : public QObject { + Q_OBJECT + +public: + explicit x0Screen(bool isOverlay = false, QObject* parent = nullptr); + virtual ~x0Screen(); + + // Screen identification + void setScreenId(const QString& id) { m_screenId = id; } + QString getScreenId() const { return m_screenId; } + + // Configuration + void setSkeletonData(const json& data) { m_skeletonData = data; } + const json& getSkeletonData() const { return m_skeletonData; } + void setJsonConfig(const json& config) { m_jsonConfig = config; } + const json& getJsonConfig() const { return m_jsonConfig; } + + // Style + void setStyle(const QString& style = QString()); + void updateStyle(const QString& style); + QString getCssStyle() const { return m_cssStyle; } + + // Setup and rendering + void setup(); + void setupObject(const QString& objectId, x0BaseObject* hierarchyObject, int hierarchyLevel = 0); + + // Root object access + std::shared_ptr getHierarchyRootObject() const { return m_hierarchyRootObject; } + + // Global variables + void setGlobalVar(const QString& key, const json& value); + json getGlobalVar(const QString& key) const; + void setGlobalVars(const json& vars); + json getGlobalVars() const; + void mergeGlobalVars(const json& items); + + // Data loading + void triggerGlobalDataLoad(); + + // Overlay indicator + bool isOverlay() const { return m_isOverlay; } + +signals: + void screenSetupComplete(); + void globalDataLoaded(const json& data); + +protected: + QString m_screenId; + json m_skeletonData; + json m_jsonConfig; + QString m_cssStyle; + bool m_isOverlay; + + std::shared_ptr m_hierarchyRootObject; + json m_globalVars; + + // Helper methods + json getSkeletonObjectsByRefId(const QString& objectId); + void processOverwriteAttributes(json& config); + void processReplaceAttributes(json& config, [[maybe_unused]] const json& refConfig); +}; + +} // namespace x0 + +#endif // X0_SCREEN_H diff --git a/cpp/src/main.cpp b/cpp/src/main.cpp new file mode 100644 index 0000000..cc6b90a --- /dev/null +++ b/cpp/src/main.cpp @@ -0,0 +1,179 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Main Application Entry Point -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//- This demonstrates the x0 C++ framework with Qt and nlohmann::json -// +//- Equivalent to the JavaScript version in /www/ -// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include +#include +#include +#include +#include +#include + +#include "x0Factory.h" +#include "x0Screen.h" +#include "x0ObjDiv.h" +#include "x0ObjButton.h" + +using namespace x0; + +/** + * @brief Load JSON configuration from file + * @param filename Path to JSON file + * @return Parsed JSON object + */ +json loadJsonFromFile(const QString& filename) { + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Could not open file:" << filename; + return json(); + } + + QTextStream in(&file); + QString content = in.readAll(); + file.close(); + + try { + return json::parse(content.toStdString()); + } catch (const json::parse_error& e) { + qWarning() << "JSON parse error:" << e.what(); + return json(); + } +} + +/** + * @brief Create a demo configuration for testing + * @return Demo JSON configuration + */ +json createDemoConfig() { + json skeleton = { + {"MainScreen", json::array({ + {{"MainDiv", {{"RefID", "MainScreen"}}}}, + {{"WelcomeText", {{"RefID", "MainDiv"}}}}, + {{"ActionButton", {{"RefID", "MainDiv"}}}} + })} + }; + + json dataObject = { + {"MainDiv", { + {"Type", "Div"}, + {"Attributes", { + {"Style", "main-container"}, + {"Value", ""} + }} + }}, + {"WelcomeText", { + {"Type", "Div"}, + {"Attributes", { + {"Style", "welcome-text"}, + {"Value", "Welcome to x0 C++ Framework!"} + }} + }}, + {"ActionButton", { + {"Type", "Button"}, + {"Attributes", { + {"Style", "btn btn-primary"}, + {"TextID", "BTN.DEMO"}, + {"Action", "reset"}, + {"DstObjectID", "WelcomeText"} + }} + }} + }; + + json textData = { + {"BTN.DEMO", { + {"en", "Click Me!"}, + {"de", "Klick mich!"} + }} + }; + + json config; + config["skeleton"] = skeleton; + config["dataObject"] = dataObject; + config["textData"] = textData; + config["defaultScreen"] = "MainScreen"; + + return config; +} + +/** + * @brief Main application class demonstrating x0 framework usage + */ +class x0DemoWindow : public QMainWindow { +public: + x0DemoWindow() { + setWindowTitle("x0 C++ Framework Demo"); + setMinimumSize(800, 600); + + // Create central widget and layout + auto* centralWidget = new QWidget(this); + auto* layout = new QVBoxLayout(centralWidget); + setCentralWidget(centralWidget); + + // Initialize the x0 factory with demo configuration + initializeFramework(); + + // Get the main screen's root widget and add it to layout + x0Screen* mainScreen = x0Factory::instance().getScreenById("MainScreen"); + if (mainScreen) { + auto rootObj = mainScreen->getHierarchyRootObject(); + if (rootObj && rootObj->getWidget()) { + layout->addWidget(rootObj->getWidget()); + } + } + } + +private: + void initializeFramework() { + // Create demo configuration + json config = createDemoConfig(); + + // Set up the factory with configuration + x0Factory& factory = x0Factory::instance(); + + factory.setDataSkeleton(config["skeleton"]); + factory.setDataObject(config["dataObject"]); + factory.setTextData(config["textData"]); + factory.setDefaultScreen("MainScreen"); + factory.setUserLanguage("en"); + + // Initialize the framework + factory.init(); + + qDebug() << "x0 Framework initialized successfully!"; + } +}; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + qDebug() << "Starting x0 C++ Framework Application"; + qDebug() << "===================================="; + qDebug() << ""; + qDebug() << "This application demonstrates the x0 C++ framework"; + qDebug() << "which is the Qt/C++ equivalent of the JavaScript"; + qDebug() << "framework found in /www/"; + qDebug() << ""; + qDebug() << "Features:"; + qDebug() << " - Hierarchical object model (x0BaseObject)"; + qDebug() << " - Qt widget integration (x0BaseElement)"; + qDebug() << " - JSON configuration via nlohmann::json"; + qDebug() << " - Screen management (x0Screen)"; + qDebug() << " - Event system (x0Reactor)"; + qDebug() << " - Object factory (x0Factory)"; + qDebug() << ""; + + x0DemoWindow window; + window.show(); + + return app.exec(); +} diff --git a/cpp/src/x0BaseElement.cpp b/cpp/src/x0BaseElement.cpp new file mode 100644 index 0000000..ab3253b --- /dev/null +++ b/cpp/src/x0BaseElement.cpp @@ -0,0 +1,219 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Base Element Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0BaseElement.h" +#include +#include + +namespace x0 { + +x0BaseElement::x0BaseElement(QObject* parent) + : QObject(parent) + , m_visibleState("visible") +{ +} + +x0BaseElement::~x0BaseElement() = default; + +void x0BaseElement::createWidget(const QString& objectId) +{ + m_objectId = objectId; + m_widget = std::make_unique(); + m_widget->setObjectName(objectId); +} + +QWidget* x0BaseElement::getWidget() const +{ + return m_widget.get(); +} + +void x0BaseElement::setParentWidget(QWidget* parent) +{ + if (m_widget && parent) { + m_widget->setParent(parent); + } +} + +void x0BaseElement::setStyleClass(const QString& styleClass) +{ + m_styleClass = styleClass; + m_styleClasses.clear(); + if (!styleClass.isEmpty()) { + m_styleClasses = styleClass.split(' ', Qt::SkipEmptyParts); + } + + if (m_widget) { + // Apply style classes as Qt stylesheet + m_widget->setProperty("class", styleClass); + emit styleChanged(); + } +} + +void x0BaseElement::addStyleClass(const QString& styleClass) +{ + QStringList classesToAdd = styleClass.split(' ', Qt::SkipEmptyParts); + for (const QString& cls : classesToAdd) { + if (!m_styleClasses.contains(cls)) { + m_styleClasses.append(cls); + } + } + m_styleClass = m_styleClasses.join(' '); + + if (m_widget) { + m_widget->setProperty("class", m_styleClass); + emit styleChanged(); + } +} + +void x0BaseElement::removeStyleClass(const QString& styleClass) +{ + QStringList classesToRemove = styleClass.split(' ', Qt::SkipEmptyParts); + for (const QString& cls : classesToRemove) { + m_styleClasses.removeAll(cls); + } + m_styleClass = m_styleClasses.join(' '); + + if (m_widget) { + m_widget->setProperty("class", m_styleClass); + emit styleChanged(); + } +} + +bool x0BaseElement::hasStyleClass(const QString& styleClass) const +{ + return m_styleClasses.contains(styleClass); +} + +QString x0BaseElement::getStyleClasses() const +{ + return m_styleClass; +} + +void x0BaseElement::setValue(const QString& value) +{ + m_value = value; + emit valueChanged(value); +} + +QString x0BaseElement::getValue() const +{ + return m_value; +} + +void x0BaseElement::setVisibleState(const QString& state) +{ + m_visibleState = state; + if (m_widget) { + if (state == "visible") { + m_widget->show(); + emit visibilityChanged(true); + } else if (state == "hidden") { + m_widget->hide(); + emit visibilityChanged(false); + } + } +} + +QString x0BaseElement::getVisibleState() const +{ + return m_visibleState; +} + +void x0BaseElement::switchVisibleState() +{ + if (m_visibleState == "visible") { + setVisibleState("hidden"); + } else { + setVisibleState("visible"); + } +} + +void x0BaseElement::enable() +{ + if (m_widget) { + m_widget->setEnabled(true); + } +} + +void x0BaseElement::disable() +{ + if (m_widget) { + m_widget->setEnabled(false); + } +} + +bool x0BaseElement::isEnabled() const +{ + return m_widget ? m_widget->isEnabled() : false; +} + +void x0BaseElement::setAttribute(const QString& attribute, const QString& value) +{ + m_attributes[attribute] = value; + if (m_widget) { + m_widget->setProperty(attribute.toUtf8().constData(), value); + } +} + +QString x0BaseElement::getAttribute(const QString& attribute) const +{ + return m_attributes.value(attribute); +} + +void x0BaseElement::setAttributes(const QMap& attributes) +{ + for (auto it = attributes.begin(); it != attributes.end(); ++it) { + setAttribute(it.key(), it.value()); + } +} + +void x0BaseElement::setStyleTop(int top) +{ + m_styleTop = top; +} + +void x0BaseElement::setStyleLeft(int left) +{ + m_styleLeft = left; +} + +void x0BaseElement::setStyleWidth(int width) +{ + m_styleWidth = width; +} + +void x0BaseElement::setStyleHeight(int height) +{ + m_styleHeight = height; +} + +void x0BaseElement::setStyleZIndex(int zIndex) +{ + m_styleZIndex = zIndex; +} + +void x0BaseElement::applyStyleAttributes() +{ + if (!m_widget) return; + + if (m_styleWidth >= 0) { + m_widget->setFixedWidth(m_styleWidth); + } + if (m_styleHeight >= 0) { + m_widget->setFixedHeight(m_styleHeight); + } + if (m_styleTop >= 0 || m_styleLeft >= 0) { + int x = m_styleLeft >= 0 ? m_styleLeft : m_widget->x(); + int y = m_styleTop >= 0 ? m_styleTop : m_widget->y(); + m_widget->move(x, y); + } + // Note: Z-index in Qt is handled through widget stacking order + // Use raise()/lower() or explicit layout management +} + +} // namespace x0 diff --git a/cpp/src/x0BaseObject.cpp b/cpp/src/x0BaseObject.cpp new file mode 100644 index 0000000..e11d111 --- /dev/null +++ b/cpp/src/x0BaseObject.cpp @@ -0,0 +1,277 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Base Object Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0BaseObject.h" +#include +#include + +namespace x0 { + +x0BaseObject::x0BaseObject(QObject* parent) + : x0BaseElement(parent) +{ +} + +x0BaseObject::~x0BaseObject() = default; + +void x0BaseObject::addObject(std::shared_ptr childObject) +{ + if (childObject) { + childObject->setParentObject(this); + m_childObjects.push_back(childObject); + } +} + +void x0BaseObject::removeObject(int index) +{ + if (index >= 0 && index < static_cast(m_childObjects.size())) { + m_childObjects.erase(m_childObjects.begin() + index); + } +} + +void x0BaseObject::removeObject(x0BaseObject* child) +{ + auto it = std::find_if(m_childObjects.begin(), m_childObjects.end(), + [child](const std::shared_ptr& obj) { + return obj.get() == child; + }); + + if (it != m_childObjects.end()) { + m_childObjects.erase(it); + } +} + +std::shared_ptr x0BaseObject::getChildByIndex(int index) const +{ + if (index >= 0 && index < static_cast(m_childObjects.size())) { + return m_childObjects[index]; + } + return nullptr; +} + +int x0BaseObject::getChildIndexById(const QString& id) const +{ + for (size_t i = 0; i < m_childObjects.size(); ++i) { + if (m_childObjects[i]->getObjectId() == id) { + return static_cast(i); + } + } + return -1; +} + +int x0BaseObject::getChildCount() const +{ + return static_cast(m_childObjects.size()); +} + +const std::vector>& x0BaseObject::getChildObjects() const +{ + return m_childObjects; +} + +void x0BaseObject::setParentObject(x0BaseObject* parent) +{ + m_parentObject = parent; +} + +x0BaseObject* x0BaseObject::getParentObject() const +{ + return m_parentObject; +} + +x0BaseObject* x0BaseObject::getObjectById(const QString& objectId) +{ + auto objects = getAllObjects(); + auto it = objects.find(objectId); + if (it != objects.end()) { + return it->second; + } + return nullptr; +} + +std::vector x0BaseObject::getObjectsByType(const QString& objectType) +{ + std::vector result; + auto objects = getAllObjects(); + + for (auto& pair : objects) { + if (pair.second->getObjectType() == objectType) { + result.push_back(pair.second); + } + } + + return result; +} + +std::vector x0BaseObject::getObjectsByAttribute(const QString& attribute) +{ + std::vector result; + auto objects = getAllObjects(); + + for (auto& pair : objects) { + const json& config = pair.second->getJsonConfig(); + if (config.contains("Attributes") && config["Attributes"].contains(attribute.toStdString())) { + result.push_back(pair.second); + } + } + + return result; +} + +std::map x0BaseObject::getAllObjects() +{ + std::map items; + collectObjects(items); + return items; +} + +void x0BaseObject::collectObjects(std::map& items) +{ + for (const auto& child : m_childObjects) { + items[child->getObjectId()] = child.get(); + child->collectObjects(items); + } +} + +void x0BaseObject::renderObject(const QString& prefix) +{ + // Set DOM IDs + m_parentId = prefix; + + QString setObjectId = m_objectId; + + if (!m_overrideDomObjectId) { + if (prefix.isEmpty()) { + m_objectId = setObjectId; + } else { + m_objectId = prefix + "_" + setObjectId; + } + } + + // Create widget if it doesn't exist + if (!m_widget) { + createWidget(m_objectId); + } + + // Apply styles and attributes + setStyleClass(m_styleClass); + setValue(m_value); + applyStyleAttributes(); + setVisibleState(m_visibleState); + + // Render child objects + for (const auto& child : m_childObjects) { + child->renderObject(m_objectId); + // Parent the child widget + if (child->getWidget() && m_widget) { + child->setParentWidget(m_widget.get()); + } + } +} + +void x0BaseObject::init() +{ + // Override in derived classes +} + +void x0BaseObject::reset() +{ + // Override in derived classes +} + +void x0BaseObject::updateValue() +{ + // Override in derived classes +} + +void x0BaseObject::setActivated() +{ + m_deactivated = false; + for (const auto& child : m_childObjects) { + child->setActivated(); + } +} + +void x0BaseObject::setDeactivated() +{ + m_deactivated = true; + for (const auto& child : m_childObjects) { + child->setDeactivated(); + } +} + +json x0BaseObject::getObjectData() +{ + // Default implementation returns empty JSON object + // Derived classes should override to return their specific data + return json::object(); +} + +void x0BaseObject::setObjectData(const json& data) +{ + // Default implementation stores data in config + // Derived classes can override for custom behavior + if (!data.is_null()) { + m_jsonConfig["data"] = data; + } +} + +void x0BaseObject::appendObjectData(const json& data) +{ + // Default implementation appends to existing data array + // Derived classes can override for custom behavior + if (!data.is_null()) { + if (!m_jsonConfig.contains("data")) { + m_jsonConfig["data"] = json::array(); + } + if (m_jsonConfig["data"].is_array()) { + m_jsonConfig["data"].push_back(data); + } + } +} + +void x0BaseObject::processReset() +{ + reset(); + for (const auto& child : m_childObjects) { + child->processReset(); + } +} + +void x0BaseObject::processUpdate() +{ + updateValue(); + for (const auto& child : m_childObjects) { + child->processUpdate(); + } +} + +void x0BaseObject::processEventListeners() +{ + // Process event listeners for this object + for (const auto& pair : m_eventListeners) { + qDebug() << "Processing event listener:" << pair.first; + } + + // Process event listeners for child objects + for (const auto& child : m_childObjects) { + child->processEventListeners(); + } +} + +void x0BaseObject::addEventListener(const QString& type, EventCallback callback) +{ + m_eventListeners[type] = callback; +} + +void x0BaseObject::removeEventListener(const QString& type) +{ + m_eventListeners.erase(type); +} + +} // namespace x0 diff --git a/cpp/src/x0Factory.cpp b/cpp/src/x0Factory.cpp new file mode 100644 index 0000000..11a5113 --- /dev/null +++ b/cpp/src/x0Factory.cpp @@ -0,0 +1,291 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Factory Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0Factory.h" +#include "x0ObjDiv.h" +#include "x0ObjButton.h" +#include + +namespace x0 { + +x0Factory::x0Factory(QObject* parent) + : QObject(parent) + , m_reactor(std::make_unique(this)) + , m_userLanguage("en") +{ + // Register default object types + registerObjectType("Div", []() { return std::make_shared(); }); + registerObjectType("Button", []() { return std::make_shared(); }); +} + +x0Factory::~x0Factory() = default; + +x0Factory& x0Factory::instance() +{ + static x0Factory instance; + return instance; +} + +void x0Factory::init() +{ + qDebug() << "x0Factory::init() - Initializing system"; + + // Process skeleton data and create screens + if (m_dataSkeleton.is_object()) { + for (auto& [screenId, screenData] : m_dataSkeleton.items()) { + qDebug() << "x0Factory::init() - Adding screen:" << QString::fromStdString(screenId); + addScreen(QString::fromStdString(screenId), screenData); + } + } + + // Set up all screens + for (auto& [screenId, screen] : m_screens.toStdMap()) { + screen->setup(); + } + + // Switch to default screen + if (!m_displayDefaultScreen.isEmpty()) { + switchScreen(m_displayDefaultScreen); + } + + // Dispatch init system event + m_reactor->dispatchEvent("InitSystem"); + + emit initSystemComplete(); +} + +void x0Factory::registerObjectType(const QString& type, ObjectCreator creator) +{ + m_objectCreators[type] = creator; +} + +std::shared_ptr x0Factory::createObject(const QString& type) +{ + auto it = m_objectCreators.find(type); + if (it != m_objectCreators.end()) { + return it.value()(); + } + + qDebug() << "x0Factory::createObject() - Unknown type:" << type << "- creating default Div"; + return std::make_shared(); +} + +x0Screen* x0Factory::addScreen(const QString& screenId, const json& skeletonData) +{ + auto screen = std::make_shared(); + screen->setScreenId(screenId); + screen->setSkeletonData(skeletonData); + + // Set JSON config if available + if (m_screenConfig.contains(screenId.toStdString())) { + screen->setJsonConfig(m_screenConfig[screenId.toStdString()]); + } + + m_screens[screenId] = screen; + + return screen.get(); +} + +x0Screen* x0Factory::getScreenById(const QString& screenId) +{ + auto it = m_screens.find(screenId); + if (it != m_screens.end()) { + return it.value().get(); + } + return nullptr; +} + +QMap>& x0Factory::getScreens() +{ + return m_screens; +} + +x0Screen* x0Factory::getLastScreenObject() +{ + if (m_screens.isEmpty()) return nullptr; + return m_screens.last().get(); +} + +void x0Factory::switchScreen(const QString& screenId) +{ + qDebug() << "x0Factory::switchScreen() ScreenID:" << screenId << "Current:" << m_currentScreenId; + + if (screenId.isEmpty()) return; + + x0Screen* screen = getScreenById(screenId); + if (!screen) { + qDebug() << "x0Factory::switchScreen() - Screen not found:" << screenId; + return; + } + + // Switch all screens to background + switchScreensToBackground(); + + // Update current screen ID + m_currentScreenId = screenId; + + // Switch selected screen to foreground + switchScreenToForeground(screen); + + // Trigger screen data load + triggerScreenDataLoad(screenId); + + emit screenSwitched(screenId); +} + +void x0Factory::switchScreensToBackground() +{ + for (auto& [screenId, screen] : m_screens.toStdMap()) { + auto rootObj = screen->getHierarchyRootObject(); + if (rootObj) { + rootObj->setVisibleState("hidden"); + } + } +} + +void x0Factory::switchScreenToForeground(x0Screen* screen) +{ + if (!screen) return; + + auto rootObj = screen->getHierarchyRootObject(); + if (rootObj) { + rootObj->setVisibleState("visible"); + } +} + +void x0Factory::triggerScreenDataLoad(const QString& screenId) +{ + x0Screen* screen = getScreenById(screenId); + if (screen) { + screen->triggerGlobalDataLoad(); + } +} + +x0BaseObject* x0Factory::getObjectById(const QString& objectId) +{ + for (auto& [screenId, screen] : m_screens.toStdMap()) { + auto rootObj = screen->getHierarchyRootObject(); + if (rootObj) { + x0BaseObject* result = rootObj->getObjectById(objectId); + if (result) return result; + } + } + return nullptr; +} + +std::map> x0Factory::getObjectsByAttribute(const QString& attribute) +{ + std::map> result; + + for (auto& [screenId, screen] : m_screens.toStdMap()) { + auto rootObj = screen->getHierarchyRootObject(); + if (rootObj) { + result[screenId] = rootObj->getObjectsByAttribute(attribute); + } + } + + return result; +} + +std::vector x0Factory::getObjectsByType(const QString& screenId, const QString& type) +{ + x0Screen* screen = getScreenById(screenId); + if (!screen) return {}; + + auto rootObj = screen->getHierarchyRootObject(); + if (!rootObj) return {}; + + return rootObj->getObjectsByType(type); +} + +json x0Factory::getGlobalVar(const QString& key) const +{ + if (m_globalData.contains(key.toStdString())) { + return m_globalData[key.toStdString()]; + } + return json(); +} + +QString x0Factory::getText(const QString& textId) const +{ + try { + if (m_textData.contains(textId.toStdString())) { + const json& textObj = m_textData[textId.toStdString()]; + if (textObj.contains(m_userLanguage.toStdString())) { + return QString::fromStdString(textObj[m_userLanguage.toStdString()].get()); + } + } + } catch (const std::exception& e) { + qDebug() << "x0Factory::getText() - Text not found for ID:" << textId; + } + + return "Missing Text with ID:" + textId; +} + +void x0Factory::registerUserFunction(const QString& functionId, std::function func) +{ + m_userFunctions[functionId] = func; +} + +std::function x0Factory::getUserFunction(const QString& functionId) +{ + auto it = m_userFunctions.find(functionId); + if (it != m_userFunctions.end()) { + return it.value(); + } + return nullptr; +} + +void x0Factory::resetErrorContainer() +{ + auto errorContainers = getObjectsByType(m_currentScreenId, "ErrorContainer"); + for (auto* container : errorContainers) { + if (container) { + container->reset(); + } + } +} + +void x0Factory::setupObjectRefsRecursive(const json& objDefs, x0BaseObject* refObj) +{ + if (!objDefs.is_array() || !refObj) return; + + for (const auto& objItem : objDefs) { + if (!objItem.contains("SysObject")) continue; + + QString objectId; + if (objItem.contains("id")) { + objectId = QString::fromStdString(objItem["id"].get()); + } + + // Create the object based on type or configuration + auto currentObject = createObject("Div"); // Default to Div + currentObject->setObjectId(objectId); + + if (objItem.contains("JSONAttributes")) { + json config; + config["Attributes"] = objItem["JSONAttributes"]; + currentObject->setJsonConfig(config); + } + + try { + currentObject->init(); + } catch (const std::exception& e) { + qDebug() << "x0Factory::setupObjectRefsRecursive() init error:" << e.what(); + } + + refObj->addObject(currentObject); + + // Recurse if there are nested object definitions + if (objItem.contains("ObjectDefs")) { + setupObjectRefsRecursive(objItem["ObjectDefs"], currentObject.get()); + } + } +} + +} // namespace x0 diff --git a/cpp/src/x0ObjButton.cpp b/cpp/src/x0ObjButton.cpp new file mode 100644 index 0000000..13d6469 --- /dev/null +++ b/cpp/src/x0ObjButton.cpp @@ -0,0 +1,249 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Button Widget Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0ObjButton.h" +#include "x0Factory.h" +#include + +namespace x0 { + +x0ObjButton::x0ObjButton(QObject* parent) + : x0BaseObject(parent) +{ + m_objectType = "Button"; +} + +x0ObjButton::~x0ObjButton() = default; + +void x0ObjButton::createWidget(const QString& objectId) +{ + m_objectId = objectId; + + auto* button = new QPushButton(); + button->setObjectName(objectId); + + // Connect button click signal + connect(button, &QPushButton::clicked, this, &x0ObjButton::onButtonClick); + + m_widget.reset(button); +} + +void x0ObjButton::init() +{ + if (!m_jsonConfig.contains("Attributes")) return; + + const json& attributes = m_jsonConfig["Attributes"]; + + // Set DOM value (button text) - supports both 'DOMValue' and 'Value' for flexibility + if (attributes.contains("DOMValue")) { + m_value = QString::fromStdString(attributes["DOMValue"].get()); + } else if (attributes.contains("Value")) { + m_value = QString::fromStdString(attributes["Value"].get()); + } + + // Set style + if (attributes.contains("Style")) { + m_styleClass = QString::fromStdString(attributes["Style"].get()); + } + + // Set disabled state + if (attributes.contains("Disabled") && attributes["Disabled"].get()) { + setDisabled(true); + } + + // Set click URL + if (attributes.contains("OnClick")) { + m_callUrl = QString::fromStdString(attributes["OnClick"].get()); + } + + // Set form validate flag + if (attributes.contains("Validate")) { + m_formValidate = true; + } + + // Get text from TextID if available + if (attributes.contains("TextID")) { + QString textId = QString::fromStdString(attributes["TextID"].get()); + m_value = x0Factory::instance().getText(textId); + } + + qDebug() << "x0ObjButton::init() ObjectID:" << m_objectId << "TextID:" << m_value; +} + +void x0ObjButton::setButtonText(const QString& text) +{ + m_value = text; + QPushButton* button = getButton(); + if (button) { + button->setText(text); + } +} + +QString x0ObjButton::getButtonText() const +{ + return m_value; +} + +void x0ObjButton::setDisabled(bool disabled) +{ + m_disabled = disabled; + + QPushButton* button = getButton(); + if (button) { + button->setEnabled(!disabled); + } + + // Update style for disabled state + if (disabled && !m_styleClass.isEmpty()) { + addStyleClass("disabled"); + } else { + removeStyleClass("disabled"); + } +} + +QPushButton* x0ObjButton::getButton() const +{ + return qobject_cast(m_widget.get()); +} + +void x0ObjButton::onButtonClick() +{ + qDebug() << "x0ObjButton::onButtonClick() Button clicked - ObjectID:" << m_objectId; + + if (m_disabled) { + qDebug() << "x0ObjButton::onButtonClick() Button is disabled"; + return; + } + + // Reset validation state + m_validateResultError = true; + + // Validate form if required + if (m_formValidate) { + validateForm(); + } else { + m_validateResultError = false; + } + + qDebug() << "x0ObjButton::onButtonClick() Validate result:" << m_validateResultError; + + if (!m_validateResultError) { + // Process actions + processActions(); + + // Call service if URL is set + if (!m_callUrl.isEmpty()) { + callService(); + } + } + + emit clicked(); +} + +void x0ObjButton::validateForm() +{ + if (!m_jsonConfig.contains("Attributes")) { + m_validateResultError = false; + return; + } + + const json& attributes = m_jsonConfig["Attributes"]; + + if (!attributes.contains("Validate")) { + m_validateResultError = false; + return; + } + + // In a full implementation, this would validate form fields + // For now, we assume validation passes + m_validateResultError = false; + + emit validationComplete(!m_validateResultError); +} + +void x0ObjButton::callService() +{ + qDebug() << "x0ObjButton::callService() URL:" << m_callUrl; + + addNotifyHandler(); + + // In a full implementation, this would make an HTTP/RPC call + // For now, emit signal indicating the call would be made + emit serviceCallComplete(json()); +} + +void x0ObjButton::addNotifyHandler() +{ + try { + if (!m_jsonConfig.contains("Attributes")) return; + + const json& attributes = m_jsonConfig["Attributes"]; + if (!attributes.contains("Notify")) return; + + const json& notifyAttrs = attributes["Notify"]; + qDebug() << "x0ObjButton::addNotifyHandler() Notify:" << QString::fromStdString(notifyAttrs.dump()); + + // In a full implementation, this would add to the global notification handler + + } catch (const std::exception& e) { + qDebug() << "x0ObjButton::addNotifyHandler() error:" << e.what(); + } +} + +void x0ObjButton::processActions() +{ + if (!m_jsonConfig.contains("Attributes")) return; + + const json& attributes = m_jsonConfig["Attributes"]; + + QString action; + if (attributes.contains("Action")) { + action = QString::fromStdString(attributes["Action"].get()).toLower(); + } + + qDebug() << "x0ObjButton::processActions() Action:" << action; + + if (action.isEmpty()) return; + + // Get destination object + x0BaseObject* dstObject = nullptr; + if (attributes.contains("DstObjectID")) { + QString dstObjectId = QString::fromStdString(attributes["DstObjectID"].get()); + dstObject = x0Factory::instance().getObjectById(dstObjectId); + } + + // Process action types + if (action == "enable" && dstObject) { + dstObject->setVisibleState("visible"); + } + else if (action == "disable" && dstObject) { + dstObject->setVisibleState("hidden"); + } + else if (action == "activate" && dstObject) { + dstObject->setActivated(); + } + else if (action == "deactivate" && dstObject) { + dstObject->setDeactivated(); + } + else if (action == "reset" && dstObject) { + dstObject->reset(); + } + else if (action == "switchscreen") { + if (attributes.contains("DstScreenID")) { + QString dstScreenId = QString::fromStdString(attributes["DstScreenID"].get()); + x0Factory::instance().switchScreen(dstScreenId); + } + } + + // Fire events if configured + if (attributes.contains("FireEvents")) { + x0Factory::instance().getReactor()->fireEvents(attributes["FireEvents"]); + } +} + +} // namespace x0 diff --git a/cpp/src/x0ObjDiv.cpp b/cpp/src/x0ObjDiv.cpp new file mode 100644 index 0000000..42136c8 --- /dev/null +++ b/cpp/src/x0ObjDiv.cpp @@ -0,0 +1,86 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Div Container Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0ObjDiv.h" +#include +#include + +namespace x0 { + +x0ObjDiv::x0ObjDiv(QObject* parent) + : x0BaseObject(parent) +{ + m_objectType = "Div"; +} + +x0ObjDiv::~x0ObjDiv() = default; + +void x0ObjDiv::createWidget(const QString& objectId) +{ + m_objectId = objectId; + + auto* frame = new QFrame(); + frame->setObjectName(objectId); + frame->setFrameStyle(QFrame::NoFrame); + + // Add a layout for child widgets + auto* layout = new QVBoxLayout(frame); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + m_widget.reset(frame); +} + +void x0ObjDiv::init() +{ + if (!m_jsonConfig.contains("Attributes")) return; + + const json& attributes = m_jsonConfig["Attributes"]; + + // Set DOM type if given + if (attributes.contains("DOMType")) { + m_domType = QString::fromStdString(attributes["DOMType"].get()); + } + + // Set DOM value if given + if (attributes.contains("Value")) { + m_value = QString::fromStdString(attributes["Value"].get()); + } + + // Set DOM style + if (attributes.contains("Style")) { + m_styleClass = QString::fromStdString(attributes["Style"].get()); + } +} + +void x0ObjDiv::reset() +{ + try { + if (!m_jsonConfig.contains("Attributes")) return; + + const json& attributes = m_jsonConfig["Attributes"]; + if (attributes.contains("Reset") && !attributes["Reset"].is_null()) { + m_value.clear(); + setValue(m_value); + } + } catch (const std::exception& e) { + qDebug() << "x0ObjDiv::reset() error:" << e.what(); + } +} + +void x0ObjDiv::setDomType(const QString& type) +{ + m_domType = type; +} + +QFrame* x0ObjDiv::getFrame() const +{ + return qobject_cast(m_widget.get()); +} + +} // namespace x0 diff --git a/cpp/src/x0Reactor.cpp b/cpp/src/x0Reactor.cpp new file mode 100644 index 0000000..1f0dcb0 --- /dev/null +++ b/cpp/src/x0Reactor.cpp @@ -0,0 +1,238 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Reactor Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0Reactor.h" +#include "x0BaseObject.h" +#include "x0Factory.h" +#include +#include + +namespace x0 { + +x0Reactor::x0Reactor(QObject* parent) + : QObject(parent) +{ +} + +x0Reactor::~x0Reactor() = default; + +void x0Reactor::registerEvent(const json& attributes, x0BaseObject* processObject, const QString& type) +{ + if (!attributes.contains("OnEvent")) return; + + const json& eventAttrs = attributes["OnEvent"]; + + if (!eventAttrs.contains("Events")) return; + + for (const auto& eventId : eventAttrs["Events"]) { + QString eventIdStr = QString::fromStdString(eventId.get()); + + json eventAttributes; + if (eventAttrs.contains("Attributes")) { + eventAttributes = eventAttrs["Attributes"]; + } + + QString eventType = type; + if (eventAttrs.contains("Type")) { + eventType = QString::fromStdString(eventAttrs["Type"].get()); + } + + x0Event event(eventIdStr, processObject, eventType, eventAttributes); + m_events.push_back(event); + + qDebug() << "x0Reactor::registerEvent() EventID:" << eventIdStr << "Type:" << eventType; + emit eventRegistered(eventIdStr, eventType); + } +} + +void x0Reactor::unregisterEvent(const QString& eventId, x0BaseObject* object) +{ + m_events.erase( + std::remove_if(m_events.begin(), m_events.end(), + [&](const x0Event& e) { + return e.id == eventId && e.objectRef == object; + }), + m_events.end() + ); +} + +void x0Reactor::dispatchEvent(const QString& eventId) +{ + qDebug() << "x0Reactor::dispatchEvent() EventID:" << eventId; + + for (const auto& event : m_events) { + if (event.id != eventId) continue; + + x0BaseObject* processObj = event.objectRef; + if (!processObj) continue; + + const json& config = processObj->getJsonConfig(); + json attributes; + + if (config.contains("Attributes")) { + attributes = config["Attributes"]; + } + + qDebug() << "x0Reactor::dispatchEvent() Processing event type:" << event.type; + + if (event.type == "ServiceConnector") { + qDebug() << "x0Reactor::dispatchEvent() - ServiceConnector"; + // In a full implementation, this would trigger service data loading + // processObj->processSourceObjects(); + // processObj->getServiceData(); + + } else if (event.type == "Dynpulldown") { + qDebug() << "x0Reactor::dispatchEvent() - Dynpulldown"; + // In a full implementation, this would fetch dynamic pulldown data + + } else if (event.type == "SetObjectPropertyValues") { + qDebug() << "x0Reactor::dispatchEvent() - SetObjectPropertyValues"; + // Create and immediately execute the property setter + // The SetObjectPropertyValues constructor calls callService() internally + SetObjectPropertyValues setter(event, this); + // Note: setter goes out of scope here - this is intentional as the + // constructor performs the required service call synchronously + } + } + + emit eventDispatched(eventId); +} + +void x0Reactor::fireEvents(const QStringList& events) +{ + for (const QString& eventId : events) { + dispatchEvent(eventId); + } +} + +void x0Reactor::fireEvents(const json& events) +{ + if (!events.is_array()) return; + + for (const auto& event : events) { + if (event.is_string()) { + dispatchEvent(QString::fromStdString(event.get())); + } + } +} + +QVector x0Reactor::getEventsByType(const QString& type) const +{ + QVector result; + for (const auto& event : m_events) { + if (event.type == type) { + result.push_back(event); + } + } + return result; +} + +QVector x0Reactor::getEventsForObject(x0BaseObject* object) const +{ + QVector result; + for (const auto& event : m_events) { + if (event.objectRef == object) { + result.push_back(event); + } + } + return result; +} + +void x0Reactor::clearEvents() +{ + m_events.clear(); +} + +void x0Reactor::clearEventsForObject(x0BaseObject* object) +{ + m_events.erase( + std::remove_if(m_events.begin(), m_events.end(), + [object](const x0Event& e) { return e.objectRef == object; }), + m_events.end() + ); +} + +// SetObjectPropertyValues implementation + +SetObjectPropertyValues::SetObjectPropertyValues(const x0Event& event, QObject* parent) + : QObject(parent) + , m_event(event) +{ + callService(); +} + +void SetObjectPropertyValues::callService() +{ + const json& attributes = m_event.attributes; + + if (!attributes.contains("DstProperties")) return; + + for (const auto& dstProperty : attributes["DstProperties"]) { + if (!dstProperty.contains("ObjectID")) continue; + + QString objectId = QString::fromStdString(dstProperty["ObjectID"].get()); + x0BaseObject* dstObject = x0Factory::instance().getObjectById(objectId); + + if (!dstObject) { + qDebug() << "SetObjectPropertyValues::callService() - Object not found:" << objectId; + continue; + } + + // Disable the parent if possible + x0BaseObject* parentObj = dstObject->getParentObject(); + if (parentObj) { + parentObj->disable(); + } + + // Apply style if specified + if (dstProperty.contains("SetStyle")) { + QString style = QString::fromStdString(dstProperty["SetStyle"].get()); + dstObject->addStyleClass(style); + } + } + + // In a full implementation, this would make an async service call + // For now, we emit a signal indicating completion + emit serviceCallComplete(json()); +} + +void SetObjectPropertyValues::handleServiceResponse(const json& response) +{ + const json& attributes = m_event.attributes; + + if (!attributes.contains("DstProperties")) return; + + for (const auto& dstProperty : attributes["DstProperties"]) { + if (!dstProperty.contains("ObjectID") || !dstProperty.contains("PropertyName")) continue; + + QString objectId = QString::fromStdString(dstProperty["ObjectID"].get()); + QString propertyName = QString::fromStdString(dstProperty["PropertyName"].get()); + + x0BaseObject* dstObject = x0Factory::instance().getObjectById(objectId); + if (!dstObject) continue; + + // Set the property value from response + if (response.contains(propertyName.toStdString())) { + // Apply the property value (implementation depends on property type) + } + + // Remove style if it was set + if (dstProperty.contains("SetStyle")) { + QString style = QString::fromStdString(dstProperty["SetStyle"].get()); + dstObject->removeStyleClass(style); + } + + // Enable the parent + x0BaseObject* parentObj = dstObject->getParentObject(); + if (parentObj) { + parentObj->enable(); + } + } +} + +} // namespace x0 diff --git a/cpp/src/x0Screen.cpp b/cpp/src/x0Screen.cpp new file mode 100644 index 0000000..4b1307b --- /dev/null +++ b/cpp/src/x0Screen.cpp @@ -0,0 +1,260 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Screen Implementation -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include "x0Screen.h" +#include "x0Factory.h" +#include + +namespace x0 { + +x0Screen::x0Screen(bool isOverlay, QObject* parent) + : QObject(parent) + , m_isOverlay(isOverlay) + , m_hierarchyRootObject(std::make_shared()) +{ + setStyle(); +} + +x0Screen::~x0Screen() = default; + +void x0Screen::setStyle(const QString& style) +{ + if (style.isEmpty()) { + QString defaultStyle = x0Factory::instance().getDefaultStyleScreen(); + m_cssStyle = !defaultStyle.isEmpty() ? defaultStyle : "sysScreenRoot col-10"; + } else { + m_cssStyle = style; + } +} + +void x0Screen::updateStyle(const QString& style) +{ + m_hierarchyRootObject->setStyleClass(style); +} + +void x0Screen::setup() +{ + qDebug() << "x0Screen::setup() ScreenID:" << m_screenId; + + // Set up root object + QString rootId = m_isOverlay ? m_screenId + "__overlay" : m_screenId; + m_hierarchyRootObject->setObjectId(rootId); + m_hierarchyRootObject->setStyleClass(m_cssStyle); + + // Setup object hierarchy + setupObject(m_screenId, m_hierarchyRootObject.get()); + + // Render the screen + m_hierarchyRootObject->renderObject(); + + // Process reset + m_hierarchyRootObject->processReset(); + + // Process event listeners + m_hierarchyRootObject->processEventListeners(); + + emit screenSetupComplete(); +} + +void x0Screen::setupObject(const QString& objectId, x0BaseObject* hierarchyObject, int hierarchyLevel) +{ + json skeletonObjects = getSkeletonObjectsByRefId(objectId); + + for (const auto& objectItem : skeletonObjects) { + if (!objectItem.is_object()) continue; + + for (auto it = objectItem.begin(); it != objectItem.end(); ++it) { + QString key = QString::fromStdString(it.key()); + const json& skeletonItem = it.value(); + + try { + // Get JSON config from data object + const json& dataObject = x0Factory::instance().getDataObject(); + json jsonConfig; + + if (dataObject.contains(key.toStdString())) { + jsonConfig = dataObject[key.toStdString()]; + } + + // Handle RefID merging + if (jsonConfig.contains("RefID")) { + QString refId = QString::fromStdString(jsonConfig["RefID"].get()); + if (dataObject.contains(refId.toStdString())) { + json refConfig = dataObject[refId.toStdString()]; + // Merge configs + for (auto& el : refConfig.items()) { + if (!jsonConfig.contains(el.key())) { + jsonConfig[el.key()] = el.value(); + } + } + } + processOverwriteAttributes(jsonConfig); + } + + // Create object based on type + QString objectType; + if (jsonConfig.contains("Type")) { + objectType = QString::fromStdString(jsonConfig["Type"].get()); + } + + auto newObject = x0Factory::instance().createObject(objectType); + if (!newObject) { + qDebug() << "Failed to create object of type:" << objectType; + continue; + } + + // Configure the new object + newObject->setJsonConfig(jsonConfig); + + QString newObjectId = m_isOverlay ? key + "__overlay" : key; + newObject->setObjectId(newObjectId); + newObject->setObjectType(objectType); + + // Get parent ID from skeleton + QString parentId; + if (skeletonItem.contains("ElementID") && !skeletonItem["ElementID"].is_null()) { + parentId = QString::fromStdString(skeletonItem["ElementID"].get()); + } else if (skeletonItem.contains("RefID")) { + parentId = QString::fromStdString(skeletonItem["RefID"].get()); + } + + qDebug() << "x0Screen::setupObject() ObjectID:" << key << "ParentID:" << parentId; + + // Initialize the object + newObject->init(); + + // Add to hierarchy + if (!parentId.isEmpty()) { + x0BaseObject* addToObject = x0Factory::instance().getObjectById(parentId); + if (addToObject) { + addToObject->addObject(newObject); + } + } else { + hierarchyObject->addObject(newObject); + } + + // Recurse + setupObject(key, newObject.get(), hierarchyLevel + 1); + + } catch (const std::exception& e) { + qDebug() << "x0Screen::setupObject() error for ObjectID:" << key << "error:" << e.what(); + } + } + } +} + +json x0Screen::getSkeletonObjectsByRefId(const QString& objectId) +{ + json refObjects = json::array(); + + const json& skeletonComplete = x0Factory::instance().getDataSkeleton(); + + // Check each screen in the skeleton + for (auto& [screenId, screenData] : skeletonComplete.items()) { + if (!screenData.is_array()) continue; + + for (const auto& objectItem : screenData) { + if (!objectItem.is_object()) continue; + + for (auto& [objectKey, processObj] : objectItem.items()) { + if (processObj.contains("RefID")) { + QString refId = QString::fromStdString(processObj["RefID"].get()); + if (refId == objectId) { + json addObject; + addObject[objectKey] = processObj; + refObjects.push_back(addObject); + } + } + } + } + } + + return refObjects; +} + +void x0Screen::processOverwriteAttributes(json& config) +{ + if (!config.contains("AttributesOverwrite")) return; + + const json& overwrite = config["AttributesOverwrite"]; + for (auto& [key, value] : overwrite.items()) { + if (!config.contains("Attributes")) { + config["Attributes"] = json::object(); + } + config["Attributes"][key] = value; + } +} + +void x0Screen::processReplaceAttributes(json& config, [[maybe_unused]] const json& refConfig) +{ + // Note: refConfig reserved for future use with reference config merging + + if (!config.contains("AttributesReplace")) return; + + const json& replace = config["AttributesReplace"]; + for (const auto& item : replace) { + if (!item.contains("DataSrc") || !item.contains("Data")) continue; + + const json& source = item["DataSrc"]; + const json& data = item["Data"]; + + if (!config.contains("Attributes")) { + config["Attributes"] = json::object(); + } + + if (source.size() == 1) { + config["Attributes"][source[0].get()] = data; + } else if (source.size() == 2) { + config["Attributes"][source[0].get()][source[1].get()] = data; + } + } +} + +void x0Screen::setGlobalVar(const QString& key, const json& value) +{ + m_globalVars[key.toStdString()] = value; +} + +json x0Screen::getGlobalVar(const QString& key) const +{ + if (m_globalVars.contains(key.toStdString())) { + return m_globalVars[key.toStdString()]; + } + return json(); +} + +void x0Screen::setGlobalVars(const json& vars) +{ + m_globalVars = vars; +} + +json x0Screen::getGlobalVars() const +{ + return m_globalVars; +} + +void x0Screen::mergeGlobalVars(const json& items) +{ + for (auto& [key, value] : items.items()) { + m_globalVars[key] = value; + } +} + +void x0Screen::triggerGlobalDataLoad() +{ + qDebug() << "x0Screen::triggerGlobalDataLoad() JSONConfig:" << QString::fromStdString(m_jsonConfig.dump()); + + // Trigger data load based on configuration + if (m_jsonConfig.contains("OnScreenSwitch")) { + // In a full implementation, this would make an async service call + // For now, emit signal that data loading is needed + emit globalDataLoaded(m_jsonConfig["OnScreenSwitch"]); + } +} + +} // namespace x0 diff --git a/cpp/test/test_examples.cpp b/cpp/test/test_examples.cpp new file mode 100644 index 0000000..d6bedbd --- /dev/null +++ b/cpp/test/test_examples.cpp @@ -0,0 +1,269 @@ +//-------1---------2---------3---------4---------5---------6---------7--------// +//- Copyright WEB/codeX, clickIT 2011 - 2025 -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- x0 C++ Framework - Example JSON Validation Test -// +//-------1---------2---------3---------4---------5---------6---------7--------// +//- -// +//- This test validates that the C++ framework can correctly parse and -// +//- process all example configurations from /example directory -// +//- -// +//- Build without Qt: g++ -std=c++20 test_examples.cpp -o test_examples -// +//-------1---------2---------3---------4---------5---------6---------7--------// + +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; +namespace fs = std::filesystem; + +// Test result tracking +struct TestResult { + std::string exampleName; + bool skeletonParsed = false; + bool objectParsed = false; + bool menuParsed = false; + bool factoryInitialized = false; + std::string error; +}; + +/** + * @brief Load JSON from file path + */ +json loadJsonFile(const std::string& filepath) { + std::ifstream file(filepath); + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + filepath); + } + + std::stringstream buffer; + buffer << file.rdbuf(); + + return json::parse(buffer.str()); +} + +/** + * @brief Validate skeleton JSON structure + */ +bool validateSkeleton(const json& skeleton) { + if (!skeleton.is_object()) return false; + + // Each key should be a screen with array of objects + for (auto& [screenId, screenData] : skeleton.items()) { + if (!screenData.is_array()) { + std::cerr << " Invalid skeleton: Screen " << screenId << " is not an array" << std::endl; + return false; + } + + for (const auto& item : screenData) { + if (!item.is_object()) { + std::cerr << " Invalid skeleton item in screen " << screenId << std::endl; + return false; + } + + // Each item should have RefID at minimum + for (auto& [objId, objData] : item.items()) { + if (!objData.is_object()) { + std::cerr << " Invalid object data for " << objId << std::endl; + return false; + } + if (!objData.contains("RefID")) { + std::cerr << " Missing RefID for object " << objId << std::endl; + return false; + } + } + } + } + return true; +} + +/** + * @brief Validate object JSON structure + */ +bool validateObject(const json& object) { + if (!object.is_object()) return false; + + // Each object should have Type and optionally Attributes + for (auto& [objId, objData] : object.items()) { + if (!objData.is_object()) { + std::cerr << " Invalid object: " << objId << " is not an object" << std::endl; + return false; + } + + if (!objData.contains("Type")) { + std::cerr << " Missing Type for object " << objId << std::endl; + return false; + } + } + return true; +} + +/** + * @brief Test a single example directory + */ +TestResult testExample(const std::string& examplePath, const std::string& exampleName) { + TestResult result; + result.exampleName = exampleName; + + std::string staticPath = examplePath + "/static"; + + // Check if static directory exists + if (!fs::exists(staticPath)) { + result.error = "No static directory found"; + return result; + } + + try { + // Load and validate skeleton.json + std::string skeletonPath = staticPath + "/skeleton.json"; + if (fs::exists(skeletonPath)) { + json skeleton = loadJsonFile(skeletonPath); + result.skeletonParsed = validateSkeleton(skeleton); + + if (result.skeletonParsed) { + std::cout << " ✓ skeleton.json parsed and validated" << std::endl; + } else { + std::cout << " ✗ skeleton.json validation failed" << std::endl; + } + } else { + std::cout << " - skeleton.json not found (optional)" << std::endl; + result.skeletonParsed = true; // Not an error if missing + } + + // Load and validate object.json + std::string objectPath = staticPath + "/object.json"; + if (fs::exists(objectPath)) { + json object = loadJsonFile(objectPath); + result.objectParsed = validateObject(object); + + if (result.objectParsed) { + std::cout << " ✓ object.json parsed and validated" << std::endl; + } else { + std::cout << " ✗ object.json validation failed" << std::endl; + } + } else { + std::cout << " - object.json not found (optional)" << std::endl; + result.objectParsed = true; // Not an error if missing + } + + // Load and validate menu.json + std::string menuPath = staticPath + "/menu.json"; + if (fs::exists(menuPath)) { + json menu = loadJsonFile(menuPath); + result.menuParsed = menu.is_array() || menu.is_object(); + + if (result.menuParsed) { + std::cout << " ✓ menu.json parsed successfully" << std::endl; + } else { + std::cout << " ✗ menu.json parse failed" << std::endl; + } + } else { + std::cout << " - menu.json not found (optional)" << std::endl; + result.menuParsed = true; // Not an error if missing + } + + // Configuration is ready for factory initialization + if (result.skeletonParsed && result.objectParsed) { + result.factoryInitialized = true; + std::cout << " ✓ Configuration ready for factory initialization" << std::endl; + } + + } catch (const json::parse_error& e) { + result.error = std::string("JSON parse error: ") + e.what(); + std::cerr << " ✗ " << result.error << std::endl; + } catch (const std::exception& e) { + result.error = e.what(); + std::cerr << " ✗ Error: " << result.error << std::endl; + } + + return result; +} + +/** + * @brief Main test function + */ +int main(int argc, char* argv[]) { + std::string examplesDir = "../example"; + + // Allow override via command line + if (argc > 1) { + examplesDir = argv[1]; + } + + std::cout << "=====================================================" << std::endl; + std::cout << "x0 C++ Framework - Example Configuration Test" << std::endl; + std::cout << "=====================================================" << std::endl; + std::cout << std::endl; + std::cout << "Testing examples from: " << examplesDir << std::endl; + std::cout << std::endl; + + std::vector results; + int passed = 0; + int failed = 0; + + // Iterate through example directories + if (!fs::exists(examplesDir)) { + std::cerr << "Error: Examples directory not found: " << examplesDir << std::endl; + std::cerr << "Please run from cpp/build directory or provide path as argument" << std::endl; + return 1; + } + + for (const auto& entry : fs::directory_iterator(examplesDir)) { + if (!entry.is_directory()) continue; + + std::string exampleName = entry.path().filename().string(); + + // Skip non-example directories (use compatible check for C++17 fallback) + if (!exampleName.empty() && exampleName[0] == '.') continue; + if (exampleName == "README.md") continue; + + std::cout << "Testing: " << exampleName << std::endl; + + TestResult result = testExample(entry.path().string(), exampleName); + results.push_back(result); + + bool success = result.skeletonParsed && result.objectParsed && + result.menuParsed && result.error.empty(); + + if (success) { + passed++; + std::cout << " → PASSED" << std::endl; + } else { + failed++; + std::cout << " → FAILED" << std::endl; + } + std::cout << std::endl; + } + + // Summary + std::cout << "=====================================================" << std::endl; + std::cout << "Test Summary" << std::endl; + std::cout << "=====================================================" << std::endl; + std::cout << "Total examples tested: " << (passed + failed) << std::endl; + std::cout << "Passed: " << passed << std::endl; + std::cout << "Failed: " << failed << std::endl; + std::cout << std::endl; + + if (failed > 0) { + std::cout << "Failed examples:" << std::endl; + for (const auto& result : results) { + bool success = result.skeletonParsed && result.objectParsed && + result.menuParsed && result.error.empty(); + if (!success) { + std::cout << " - " << result.exampleName; + if (!result.error.empty()) { + std::cout << ": " << result.error; + } + std::cout << std::endl; + } + } + } + + return failed > 0 ? 1 : 0; +}