From 03d2e94f8ba98f08adc7084607fc2180972323a9 Mon Sep 17 00:00:00 2001 From: Sylvain Schneider Date: Fri, 13 Mar 2026 16:34:34 +0100 Subject: [PATCH] Work on the graphical interface for presenting requirements details --- src/api/project.h | 10 +- src/api/requirement.h | 145 +++++++++-- .../projectManager/RequirementManager.cpp | 2 +- src/core/projectManager/RequirementManager.h | 2 - src/core/projectManager/projectManager.cpp | 232 +++++++++++++++-- src/core/projectManager/projectManager.h | 16 +- src/core/projectManager/requirementItem.cpp | 71 ++++-- src/core/projectManager/requirementItem.h | 12 +- src/gui/mainFrame.cpp | 9 +- src/gui/mainFrame.h | 8 +- .../requirementDetailPanel.cpp | 233 ++++++++++++++++++ .../requirementDetailPanel.h | 35 +++ .../requirementListPanel.cpp | 23 ++ .../requirementListPanel.h | 28 +++ .../requirementsPanel/requirementsPanel.cpp | 40 +++ src/gui/requirementsPanel/requirementsPanel.h | 68 +++++ src/gui/wxNode.h | 7 +- 17 files changed, 859 insertions(+), 82 deletions(-) create mode 100644 src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.cpp create mode 100644 src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.h create mode 100644 src/gui/requirementsPanel/requirementListPanel/requirementListPanel.cpp create mode 100644 src/gui/requirementsPanel/requirementListPanel/requirementListPanel.h create mode 100644 src/gui/requirementsPanel/requirementsPanel.cpp create mode 100644 src/gui/requirementsPanel/requirementsPanel.h diff --git a/src/api/project.h b/src/api/project.h index 92ab05f..57eb072 100644 --- a/src/api/project.h +++ b/src/api/project.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace api::project @@ -18,8 +19,6 @@ enum class OperationType : uint8_t /* --- */ -//-------------------------------------------------------------- -// Post event structs //-------------------------------------------------------------- struct ProjectOperationEvent : dBus::api::DefaultData { @@ -62,12 +61,6 @@ struct ProjectOperationEvent : dBus::api::DefaultData(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); } //-------------------------------------------------------------- +using ProjectOperationMessage_ptr = std::shared_ptr>; } // namespace api::project diff --git a/src/api/requirement.h b/src/api/requirement.h index 6f8c4b8..a136eb4 100644 --- a/src/api/requirement.h +++ b/src/api/requirement.h @@ -2,6 +2,7 @@ #include #include +#include namespace api::requirement { @@ -27,7 +28,7 @@ struct Metadata //-------------------------------------------------------------- struct Details { - std::string name; // Short name or title of the requirement + 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) @@ -52,42 +53,146 @@ struct Requirement /* --- */ //-------------------------------------------------------------- -// Post event structs -//-------------------------------------------------------------- -struct RequirementEvent : dBus::api::DefaultData +struct GetRequirementEvent : dBus::api::DefaultData { - virtual ~RequirementEvent() = default; // Default destructor + virtual ~GetRequirementEvent() = default; // Default destructor - Requirement requirementData; + std::string uuid; // UUID of the requirement (empty for root-level requirements) [[nodiscard]] std::string toString() const override { - // return std::format("RequirementEvent: id={}, name={}, subRequirementsCount={}", - // requirementData.id, - // requirementData.name, - // requirementData.description); - return ""; + return std::format("GetRequirementEvent: uuid={}", uuid); } }; //-------------------------------------------------------------- +inline std::expected postGetRequirementRequest(dBus::Bus &bus, const std::string &uuid) +{ + auto eventData = GetRequirementEvent(); + eventData.uuid = std::move(uuid); + return dBus::api::requestData(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +} +//-------------------------------------------------------------- +using GetRequirementMessage_ptr = std::shared_ptr>; /* --- */ //-------------------------------------------------------------- -// Post event helpers +struct GetChildrenRequirementEvent : dBus::api::DefaultData +{ + 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, dBus::ReturnStatus> postGetChildrenRequirementRequest(dBus::Bus &bus, const std::string &uuid) +{ + auto eventData = GetChildrenRequirementEvent(); + eventData.uuid = std::move(uuid); + return dBus::api::requestData>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +} +//-------------------------------------------------------------- +using GetChildrenRequirementMessage_ptr = std::shared_ptr>>; + +/* --- */ + +//-------------------------------------------------------------- +struct AddRequirementEvent : dBus::api::DefaultData +{ + 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(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +} +//-------------------------------------------------------------- +using AddRequirementMessage_ptr = std::shared_ptr>; + +/* --- */ + +//-------------------------------------------------------------- +struct UpdateRequirementEvent : dBus::api::DefaultData +{ + 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(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +} +//-------------------------------------------------------------- +using UpdateRequirementMessage_ptr = std::shared_ptr>; + +/* --- */ + +//-------------------------------------------------------------- +struct DeleteRequirementEvent : dBus::api::DefaultData +{ + 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(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +} +//-------------------------------------------------------------- +using DeleteRequirementMessage_ptr = std::shared_ptr>; + +/* --- */ + /* 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(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); -} +// inline dBus::api::PostReturnStatus postRequirementRequest(dBus::Bus &bus, +// Requirement requirement) +//{ +// auto eventData = RequirementEvent(); +// eventData.requirementData = std::move(requirement); +// +// return dBus::api::requestData(bus, eventData.getID(), eventData, dBus::MessageCategory::UI); +// } //-------------------------------------------------------------- } // namespace api::requirement diff --git a/src/core/projectManager/RequirementManager.cpp b/src/core/projectManager/RequirementManager.cpp index ac17690..25778d2 100644 --- a/src/core/projectManager/RequirementManager.cpp +++ b/src/core/projectManager/RequirementManager.cpp @@ -1,4 +1,4 @@ -#include "RequirementManager.h" +#include "requirementManager.h" #include "requirementItem.h" diff --git a/src/core/projectManager/RequirementManager.h b/src/core/projectManager/RequirementManager.h index 31d83bc..1da6503 100644 --- a/src/core/projectManager/RequirementManager.h +++ b/src/core/projectManager/RequirementManager.h @@ -35,8 +35,6 @@ class RequirementManager protected: RequirementItemPtr m_rootRequirement; // Root requirement of the current project std::unordered_map m_requirementsByUuid; // Map of requirement UUID to requirement pointer for quick lookup - - private: }; //-------------------------------------------------------------- } // namespace core::project diff --git a/src/core/projectManager/projectManager.cpp b/src/core/projectManager/projectManager.cpp index 6a52d77..023266e 100644 --- a/src/core/projectManager/projectManager.cpp +++ b/src/core/projectManager/projectManager.cpp @@ -1,6 +1,7 @@ #include "projectManager.h" -#include "RequirementManager.h" +#include "requirementItem.h" +#include "requirementManager.h" using namespace std; using namespace core::project; @@ -33,8 +34,13 @@ void ProjectManager::saveFile(const std::filesystem::path &filePath) /* Run the bus listener thread */ void ProjectManager::runBusListener() { - // Subscribe to the bus for messages related to log entries + // 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. */ @@ -79,43 +85,75 @@ void ProjectManager::processMessageBus(const Message_ptr &message) const auto messageType = message->getMessageTypeID(); switch (messageType) { + // Process project events case dBus::makeID("project.file.operation"): { - // Process project operation request - on_projectOperationEvent(message->as>()); + on_projectOperationEvent(message); break; } + + // Process requirement events + case dBus::makeID("requirement.get"): + { + on_requirementGetEvent(message->as>()); + break; + } + case dBus::makeID("requirement.getChildren"): + { + on_requirementGetChildrenEvent(message->as>>()); + break; + } + case dBus::makeID("requirement.add"): + { + on_requirementAddEvent(message->as>()); + break; + } + case dBus::makeID("requirement.update"): + { + on_requirementUpdateEvent(message->as>()); + break; + } + case dBus::makeID("requirement.delete"): + { + on_requirementDeleteEvent(message->as>()); + break; + } + + default: + break; } } //-------------------------------------------------------------- /* Process a file operation event */ -void ProjectManager::on_projectOperationEvent(const ProjectOperationMessage_ptr &message) +void ProjectManager::on_projectOperationEvent(const Message_ptr &message) { + const auto specializedMessage = message->as>(); + // Sanity check - if (!message) + if (!specializedMessage) return; - switch (message->value.operationType) + switch (specializedMessage->value.operationType) { using enum api::project::OperationType; case Create: - on_projectCreateFileEvent(message); + on_projectCreateFileEvent(specializedMessage); break; case Open: - on_projectOpenFileEvent(message); + on_projectOpenFileEvent(specializedMessage); break; case Save: case SaveAs: - on_projectSaveFileEvent(message); + on_projectSaveFileEvent(specializedMessage); break; case Close: - on_projectCloseFileEvent(message); + on_projectCloseFileEvent(specializedMessage); break; } } //-------------------------------------------------------------- /* Process a file creation event */ -void ProjectManager::on_projectCreateFileEvent(const ProjectOperationMessage_ptr &message) +void ProjectManager::on_projectCreateFileEvent(const api::project::ProjectOperationMessage_ptr &message) { m_requirementManager = make_shared(); @@ -123,22 +161,22 @@ void ProjectManager::on_projectCreateFileEvent(const ProjectOperationMessage_ptr } //-------------------------------------------------------------- /* Process a file open event */ -void ProjectManager::on_projectOpenFileEvent(const ProjectOperationMessage_ptr &message) +void ProjectManager::on_projectOpenFileEvent(const api::project::ProjectOperationMessage_ptr &message) { - // message->responsePromise.set_exception(std::make_exception_ptr(std::runtime_error("Open operation not implemented"))); try { openFile(message->value.filePath); + + message->responsePromise.set_value(); } catch (const std::exception &e) { message->responsePromise.set_exception(std::make_exception_ptr(e)); } - message->responsePromise.set_value(); } //-------------------------------------------------------------- /* Process a file save event */ -void ProjectManager::on_projectSaveFileEvent(const ProjectOperationMessage_ptr &message) +void ProjectManager::on_projectSaveFileEvent(const api::project::ProjectOperationMessage_ptr &message) { try { @@ -154,18 +192,176 @@ void ProjectManager::on_projectSaveFileEvent(const ProjectOperationMessage_ptr & 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)); } - message->responsePromise.set_value(); } //-------------------------------------------------------------- /* Process a file close event */ -void ProjectManager::on_projectCloseFileEvent(const ProjectOperationMessage_ptr &message) +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 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)); + } +} +//-------------------------------------------------------------- diff --git a/src/core/projectManager/projectManager.h b/src/core/projectManager/projectManager.h index d8379ea..905223f 100644 --- a/src/core/projectManager/projectManager.h +++ b/src/core/projectManager/projectManager.h @@ -41,12 +41,16 @@ class ProjectManager : public dBus::Node std::jthread m_busListenerThread; // Thread for listening to bus messages related to requirements // Events processing - using ProjectOperationMessage_ptr = std::shared_ptr>; - void on_projectOperationEvent(const ProjectOperationMessage_ptr &message); // Process a file operation event - void on_projectCreateFileEvent(const ProjectOperationMessage_ptr &message); // Process a file creation event - void on_projectOpenFileEvent(const ProjectOperationMessage_ptr &message); // Process a file opening event - void on_projectSaveFileEvent(const ProjectOperationMessage_ptr &message); // Process a file saving event - void on_projectCloseFileEvent(const ProjectOperationMessage_ptr &message); // Process a file closing event + 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 diff --git a/src/core/projectManager/requirementItem.cpp b/src/core/projectManager/requirementItem.cpp index b3c6841..971223b 100644 --- a/src/core/projectManager/requirementItem.cpp +++ b/src/core/projectManager/requirementItem.cpp @@ -1,6 +1,6 @@ #include "requirementItem.h" -#include "RequirementManager.h" +#include "requirementManager.h" using namespace std; using namespace core::project; @@ -17,21 +17,45 @@ RequirementItem::RequirementItem(RequirementManager &manager, const std::shared_ m_requirementManager.registerRequirement(shared_from_this()); } //-------------------------------------------------------------- +/* 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 api::requirement::Requirement &child) +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(m_requirementManager); - childItem->metadata = child.metadata; - childItem->details = child.details; - childItem->classification = child.classification; + const auto childItem = make_shared(m_requirementManager, shared_from_this()); + childItem->update(child); // Update the child requirement item with the provided child requirement data - // Prepare requirement links - childItem->metadata.uuid = m_requirementManager.generateUniqueUuid(); // Generate a unique UUID for this requirement item - childItem->m_parent = shared_from_this(); - - // Register this requirement item in the manager's internal map - m_requirementManager.registerRequirement(childItem); + // 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 */ @@ -41,11 +65,24 @@ void RequirementItem::removeChild(const std::string &childUuid) m_requirementManager.unregisterRequirement(childUuid); // Find the child requirement item with the given UUID - const auto it = ranges::find_if(m_children, - [&childUuid](const RequirementItemPtr &child) - { return child->metadata.uuid == childUuid; }); - if (it != m_children.end()) - m_children.erase(it); // Remove the child requirement item from the vector of children + 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 */ diff --git a/src/core/projectManager/requirementItem.h b/src/core/projectManager/requirementItem.h index fdd89eb..6bd8349 100644 --- a/src/core/projectManager/requirementItem.h +++ b/src/core/projectManager/requirementItem.h @@ -24,10 +24,16 @@ class RequirementItem : public std::enable_shared_from_this explicit RequirementItem(RequirementManager &manager, // Constructor const std::shared_ptr &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 - void appendChild(const api::requirement::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 - [[nodiscard]] std::vector getChildren() const; // Get the vector of child requirement items of this requirement item + 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 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 diff --git a/src/gui/mainFrame.cpp b/src/gui/mainFrame.cpp index a3272c5..8d1acbb 100644 --- a/src/gui/mainFrame.cpp +++ b/src/gui/mainFrame.cpp @@ -3,6 +3,7 @@ #include "bars/menuBar.h" #include "bars/statusBar.h" #include "bars/toolBar.h" +#include "requirementsPanel/requirementsPanel.h" #include @@ -18,7 +19,6 @@ using namespace gui; MainFrame::MainFrame(dBus::Bus &bus) : wxFrame(nullptr, wxID_ANY, _("kwa.Fr"), wxDefaultPosition, wxDefaultSize) , dBus::wxNode(this, bus) - , m_bus(bus) { // Initialization SetIcon(wxICON(AAAA_ICON)); @@ -36,6 +36,8 @@ MainFrame::MainFrame(dBus::Bus &bus) CenterOnScreen(); subscribe(dBus::makeID("log.message")); + + // Iconize(); } //-------------------------------------------------------------- /* Creating controls */ @@ -46,7 +48,10 @@ void MainFrame::createControls() 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_pageNotebook = new wxNotebook(framePanel, wxID_ANY); + m_requirementsPanel = new RequirementsPanel(m_pageNotebook, m_bus); + + m_pageNotebook->AddPage(m_requirementsPanel, "Requirements", true); // Controls positioning const auto mainSizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/gui/mainFrame.h b/src/gui/mainFrame.h index c16ac8a..088a9ed 100644 --- a/src/gui/mainFrame.h +++ b/src/gui/mainFrame.h @@ -11,6 +11,7 @@ namespace gui class MenuBar; class StatusBar; class ToolBar; +class RequirementsPanel; //-------------------------------------------------------------- class MainFrame : public wxFrame , public dBus::wxNode @@ -18,7 +19,7 @@ class MainFrame : public wxFrame public: MainFrame() = delete; // Default constructor virtual ~MainFrame() = default; // Default destructor - MainFrame(MainFrame &obj) = delete; // Copy constructor + 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 @@ -26,13 +27,14 @@ class MainFrame : public wxFrame explicit MainFrame(dBus::Bus &bus); // Constructor protected: - dBus::Bus &m_bus; // Reference to the application bus - + // 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 diff --git a/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.cpp b/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.cpp new file mode 100644 index 0000000..49541aa --- /dev/null +++ b/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.cpp @@ -0,0 +1,233 @@ +#include "requirementDetailPanel.h" + +using namespace std; +using namespace gui; +//-------------------------------------------------------------- +/* Constructor */ +RequirementDetailPanel::RequirementDetailPanel(wxWindow *parentWindow, dBus::Bus &bus) + : wxScrolledWindow(parentWindow, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxVSCROLL) + , m_bus(bus) +{ + // Initialization + SetScrollRate(0, 10); // Set the scroll rate for the panel (vertical only) + + // Creating controls + createControls(); + + // Post initialization +} +//-------------------------------------------------------------- +/* Creating controls */ +void RequirementDetailPanel::createControls() +{ + const auto mainPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(500, -1), wxSIMPLE_BORDER); + + // Controls positioning + const auto ctrlSizer = new wxBoxSizer(wxVERTICAL); + ctrlSizer->Add(createControls_metadata(mainPanel), wxSizerFlags(0).Expand()); + ctrlSizer->Add(createControls_details(mainPanel), wxSizerFlags(0).Expand()); + ctrlSizer->Add(createControls_classification(mainPanel), wxSizerFlags(0).Expand()); + mainPanel->SetSizer(ctrlSizer); + + const auto mainSizer = new wxBoxSizer(wxHORIZONTAL); + mainSizer->Add(mainPanel, wxSizerFlags(0).Expand()); + mainSizer->AddStretchSpacer(1); + + SetSizer(mainSizer); + + // Set the virtual size to match the size of the child panel, + // enabling scrolling if necessary + 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) +{ + struct Item + { + wxWindow *titleCtrl; + wxWindow *inputCtrl; + }; + std::vector items; + + { + const Item item{ .titleCtrl = createTitle(owner, _T("UUID:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, _T("ID:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, _("Author:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, _("Created at:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, _("Updated at:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + + // Controls positioning + const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5); + for (const auto &item : items) + { + ctrlSizer->Add(item.titleCtrl, wxSizerFlags(0).Expand().CenterVertical()); + ctrlSizer->Add(item.inputCtrl, 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) +{ + struct Item + { + wxWindow *titleCtrl; + wxWindow *inputCtrl; + }; + std::vector items; + + { + const Item item{ .titleCtrl = createTitle(owner, _("Title:")), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, _("Description:")), + .inputCtrl = new wxTextCtrl(owner, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxDefaultSize, + wxTE_PROCESS_TAB | wxTE_MULTILINE | wxVSCROLL | wxTE_AUTO_URL) }; + items.push_back(item); + + item.inputCtrl->SetMinSize(wxSize(-1, 200)); + } + { + const Item item{ .titleCtrl = createTitle(owner, _("Acceptance criteria:")), + .inputCtrl = new wxTextCtrl(owner, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxDefaultSize, + wxTE_PROCESS_TAB | wxTE_MULTILINE | wxVSCROLL) }; + items.push_back(item); + + item.inputCtrl->SetMinSize(wxSize(-1, 100)); + } + { + const Item item{ .titleCtrl = new wxPanel(owner), + .inputCtrl = new wxCheckBox(owner, wxID_ANY, _("S.M.A.R.T.")) }; + items.push_back(item); + } + + // Controls positioning + const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5); + for (const auto &item : items) + { + ctrlSizer->Add(item.titleCtrl, wxSizerFlags(0).Expand().CenterVertical()); + ctrlSizer->Add(item.inputCtrl, 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) +{ + struct Item + { + wxWindow *titleCtrl; + wxWindow *inputCtrl; + }; + std::vector items; + + { + const Item item{ .titleCtrl = createTitle(owner, "Type:"), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, "Category:"), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, "Priority:"), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + { + const Item item{ .titleCtrl = createTitle(owner, "Status:"), + .inputCtrl = new wxTextCtrl(owner, wxID_ANY) }; + items.push_back(item); + } + + // Controls positioning + const auto ctrlSizer = new wxFlexGridSizer(2, 5, 5); + for (const auto &item : items) + { + ctrlSizer->Add(item.titleCtrl, wxSizerFlags(0).Expand().CenterVertical()); + ctrlSizer->Add(item.inputCtrl, 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); + + 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)); + } +} +//-------------------------------------------------------------- diff --git a/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.h b/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.h new file mode 100644 index 0000000..5da44ce --- /dev/null +++ b/src/gui/requirementsPanel/requirementDetailPanel/requirementDetailPanel.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +namespace gui +{ +//-------------------------------------------------------------- +class RequirementDetailPanel : public wxScrolledWindow +{ + 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 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 diff --git a/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.cpp b/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.cpp new file mode 100644 index 0000000..5e5c0bc --- /dev/null +++ b/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.cpp @@ -0,0 +1,23 @@ +#include "requirementListPanel.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() +{ +} +//-------------------------------------------------------------- diff --git a/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.h b/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.h new file mode 100644 index 0000000..2ff9b34 --- /dev/null +++ b/src/gui/requirementsPanel/requirementListPanel/requirementListPanel.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +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 + + private: + void createControls(); // Creating controls +}; +//-------------------------------------------------------------- +} // namespace gui diff --git a/src/gui/requirementsPanel/requirementsPanel.cpp b/src/gui/requirementsPanel/requirementsPanel.cpp new file mode 100644 index 0000000..02f94e9 --- /dev/null +++ b/src/gui/requirementsPanel/requirementsPanel.cpp @@ -0,0 +1,40 @@ +#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 + + // 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)); + + SetSizer(mainSizer); +} +//-------------------------------------------------------------- +/* dBus message reception handler */ +void RequirementsPanel::on_receiveMessageFromBus(MessageID messageID, MessagePtr message) +{ +} +//-------------------------------------------------------------- diff --git a/src/gui/requirementsPanel/requirementsPanel.h b/src/gui/requirementsPanel/requirementsPanel.h new file mode 100644 index 0000000..a2ea94e --- /dev/null +++ b/src/gui/requirementsPanel/requirementsPanel.h @@ -0,0 +1,68 @@ +#pragma once + +#include "gui/wxNode.h" + +#include +#include + +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] │ +└─────────────────────────────────────────────┘ + */ \ No newline at end of file diff --git a/src/gui/wxNode.h b/src/gui/wxNode.h index 87fb3d4..ea58fbe 100644 --- a/src/gui/wxNode.h +++ b/src/gui/wxNode.h @@ -67,9 +67,12 @@ class wxNode : private dBus::Node // 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 + 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