Work on the graphical interface for presenting requirements details

This commit is contained in:
Sylvain Schneider
2026-03-13 16:34:34 +01:00
parent 25fc14d6bd
commit 03d2e94f8b
17 changed files with 859 additions and 82 deletions

View File

@@ -1,6 +1,7 @@
#pragma once
#include <dbus/api/api.h>
#include <dbus/dBus.h>
#include <filesystem>
namespace api::project
@@ -18,8 +19,6 @@ enum class OperationType : uint8_t
/* --- */
//--------------------------------------------------------------
// Post event structs
//--------------------------------------------------------------
struct ProjectOperationEvent : dBus::api::DefaultData<dBus::makeID("project.file.operation")>
{
@@ -62,12 +61,6 @@ struct ProjectOperationEvent : dBus::api::DefaultData<dBus::makeID("project.file
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Post event helpers
//--------------------------------------------------------------
inline dBus::api::PostReturnStatus postProjectOperationRequest(dBus::Bus &bus, const OperationType &operationType, const std::filesystem::path &filePath = "")
{
auto eventData = ProjectOperationEvent();
@@ -77,4 +70,5 @@ inline dBus::api::PostReturnStatus postProjectOperationRequest(dBus::Bus &bus, c
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using ProjectOperationMessage_ptr = std::shared_ptr<dBus::RequestMessage<ProjectOperationEvent, void>>;
} // namespace api::project

View File

@@ -2,6 +2,7 @@
#include <chrono>
#include <dbus/api/api.h>
#include <dbus/dBus.h>
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<dBus::makeID("requirement.requirementEvent")>
struct GetRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.get")>
{
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<Requirement, dBus::ReturnStatus> postGetRequirementRequest(dBus::Bus &bus, const std::string &uuid)
{
auto eventData = GetRequirementEvent();
eventData.uuid = std::move(uuid);
return dBus::api::requestData<decltype(eventData), Requirement>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using GetRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<GetRequirementEvent, Requirement>>;
/* --- */
//--------------------------------------------------------------
// Post event helpers
struct GetChildrenRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.getChildren")>
{
virtual ~GetChildrenRequirementEvent() = default; // Default destructor
std::string uuid; // UUID of the requirement (empty for root-level requirements)
[[nodiscard]] std::string toString() const override
{
return std::format("GetChildrenRequirementEvent: uuid={}", uuid);
}
};
//--------------------------------------------------------------
inline std::expected<std::vector<Requirement>, dBus::ReturnStatus> postGetChildrenRequirementRequest(dBus::Bus &bus, const std::string &uuid)
{
auto eventData = GetChildrenRequirementEvent();
eventData.uuid = std::move(uuid);
return dBus::api::requestData<decltype(eventData), std::vector<Requirement>>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using GetChildrenRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<GetChildrenRequirementEvent, std::vector<Requirement>>>;
/* --- */
//--------------------------------------------------------------
struct AddRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.add")>
{
virtual ~AddRequirementEvent() = default; // Default destructor
std::string parentUuid; // UUID of the parent requirement (empty for root-level requirements)
Requirement requirementData; // Data of the requirement to be added
[[nodiscard]] std::string toString() const override
{
return std::format("AddRequirementEvent: parentUuid={}, id={}, title={}",
parentUuid,
requirementData.metadata.id,
requirementData.details.title);
}
};
//--------------------------------------------------------------
inline dBus::api::PostReturnStatus postAddRequirementRequest(dBus::Bus &bus, const std::string &parentUuid, const Requirement &requirement)
{
auto eventData = AddRequirementEvent();
eventData.parentUuid = std::move(parentUuid);
eventData.requirementData = std::move(requirement);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using AddRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<AddRequirementEvent, void>>;
/* --- */
//--------------------------------------------------------------
struct UpdateRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.update")>
{
virtual ~UpdateRequirementEvent() = default; // Default destructor
Requirement requirementData; // Data of the requirement to be updated
[[nodiscard]] std::string toString() const override
{
return std::format("UpdateRequirementEvent: id={}, title={}",
requirementData.metadata.id,
requirementData.details.title);
}
};
//--------------------------------------------------------------
inline dBus::api::PostReturnStatus postUpdateRequirementRequest(dBus::Bus &bus, const Requirement &requirement)
{
auto eventData = UpdateRequirementEvent();
eventData.requirementData = std::move(requirement);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using UpdateRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<UpdateRequirementEvent, void>>;
/* --- */
//--------------------------------------------------------------
struct DeleteRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.delete")>
{
virtual ~DeleteRequirementEvent() = default; // Default destructor
Requirement requirementData; // Data of the requirement to be deleted
[[nodiscard]] std::string toString() const override
{
return std::format("DeleteRequirementEvent: id={}, title={}",
requirementData.metadata.id,
requirementData.details.title);
}
};
//--------------------------------------------------------------
inline dBus::api::PostReturnStatus postDeleteRequirementRequest(dBus::Bus &bus, const Requirement &requirement)
{
auto eventData = DeleteRequirementEvent();
eventData.requirementData = std::move(requirement);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
using DeleteRequirementMessage_ptr = std::shared_ptr<dBus::RequestMessage<DeleteRequirementEvent, void>>;
/* --- */
/* Post a requirement event to the bus. This function will post a
* RequirementEvent containing the provided requirement data to the
* bus, and return a PostReturnStatus indicating whether the post was
* successful or if there were any issues (e.g., no subscribers,
* timeout, etc.). */
inline dBus::api::PostReturnStatus postRequirementRequest(dBus::Bus &bus,
Requirement requirement)
{
auto eventData = RequirementEvent();
eventData.requirementData = std::move(requirement);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
// inline dBus::api::PostReturnStatus postRequirementRequest(dBus::Bus &bus,
// Requirement requirement)
//{
// auto eventData = RequirementEvent();
// eventData.requirementData = std::move(requirement);
//
// return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
// }
//--------------------------------------------------------------
} // namespace api::requirement

View File

@@ -1,4 +1,4 @@
#include "RequirementManager.h"
#include "requirementManager.h"
#include "requirementItem.h"

View File

@@ -35,8 +35,6 @@ class RequirementManager
protected:
RequirementItemPtr m_rootRequirement; // Root requirement of the current project
std::unordered_map<std::string, RequirementItemPtr> m_requirementsByUuid; // Map of requirement UUID to requirement pointer for quick lookup
private:
};
//--------------------------------------------------------------
} // namespace core::project

View File

@@ -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<dBus::RequestMessage<api::project::ProjectOperationEvent, void>>());
on_projectOperationEvent(message);
break;
}
// Process requirement events
case dBus::makeID("requirement.get"):
{
on_requirementGetEvent(message->as<dBus::RequestMessage<api::requirement::GetRequirementEvent, api::requirement::Requirement>>());
break;
}
case dBus::makeID("requirement.getChildren"):
{
on_requirementGetChildrenEvent(message->as<dBus::RequestMessage<api::requirement::GetChildrenRequirementEvent, std::vector<api::requirement::Requirement>>>());
break;
}
case dBus::makeID("requirement.add"):
{
on_requirementAddEvent(message->as<dBus::RequestMessage<api::requirement::AddRequirementEvent, void>>());
break;
}
case dBus::makeID("requirement.update"):
{
on_requirementUpdateEvent(message->as<dBus::RequestMessage<api::requirement::UpdateRequirementEvent, void>>());
break;
}
case dBus::makeID("requirement.delete"):
{
on_requirementDeleteEvent(message->as<dBus::RequestMessage<api::requirement::DeleteRequirementEvent, void>>());
break;
}
default:
break;
}
}
//--------------------------------------------------------------
/* Process a file operation event */
void ProjectManager::on_projectOperationEvent(const ProjectOperationMessage_ptr &message)
void ProjectManager::on_projectOperationEvent(const Message_ptr &message)
{
const auto specializedMessage = message->as<dBus::RequestMessage<api::project::ProjectOperationEvent, void>>();
// 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<RequirementManager>();
@@ -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<api::requirement::Requirement> childrenData;
childrenData.reserve(children.size());
for (const auto &child : children)
childrenData.push_back(child->toRequirement());
message->responsePromise.set_value(childrenData);
}
catch (const std::exception &e)
{
message->responsePromise.set_exception(std::make_exception_ptr(e));
}
}
//--------------------------------------------------------------
/* Process a requirement addition event */
void ProjectManager::on_requirementAddEvent(const api::requirement::AddRequirementMessage_ptr &message) const
{
// Sanity check
if (!message)
return;
try
{
// Sanity check
if (!m_requirementManager)
throw std::runtime_error("No project file is currently opened");
// Get parent requirement handle
const auto parentRequirement = message->value.parentUuid.empty()
? m_requirementManager->getRootRequirement()
: m_requirementManager->getRequirement(message->value.parentUuid);
if (!parentRequirement)
throw std::runtime_error("Parent requirement not found");
// Append the new requirement to the parent requirement
parentRequirement->appendChild(message->value.requirementData);
message->responsePromise.set_value();
}
catch (const std::exception &e)
{
message->responsePromise.set_exception(std::make_exception_ptr(e));
}
}
//--------------------------------------------------------------
/* Process a requirement update event */
void ProjectManager::on_requirementUpdateEvent(const api::requirement::UpdateRequirementMessage_ptr &message) const
{
// Sanity check
if (!message)
return;
try
{
// Sanity check
if (!m_requirementManager)
throw std::runtime_error("No project file is currently opened");
// Get requirement handle
const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
if (!requirement)
throw std::runtime_error("Requirement not found");
// Update the requirement data
requirement->update(message->value.requirementData);
message->responsePromise.set_value();
}
catch (const std::exception &e)
{
message->responsePromise.set_exception(std::make_exception_ptr(e));
}
}
//--------------------------------------------------------------
/* Process a requirement deletion event */
void ProjectManager::on_requirementDeleteEvent(const api::requirement::DeleteRequirementMessage_ptr &message) const
{
// Sanity check
if (!message)
return;
try
{
// Sanity check
if (!m_requirementManager)
throw std::runtime_error("No project file is currently opened");
// Get requirement handle
const auto requirement = m_requirementManager->getRequirement(message->value.requirementData.metadata.uuid);
if (!requirement)
throw std::runtime_error("Requirement not found");
// Get parent requirement handle
const auto parentRequirement = requirement->getParent();
if (!parentRequirement)
{
// If the requirement to be deleted is the root requirement,
// we cannot delete it as it is the base of the requirements hierarchy
throw std::runtime_error("Cannot delete root requirement");
}
// Remove the requirement from its parent
parentRequirement->removeChild(requirement->metadata.uuid);
message->responsePromise.set_value();
}
catch (const std::exception &e)
{
message->responsePromise.set_exception(std::make_exception_ptr(e));
}
}
//--------------------------------------------------------------

View File

@@ -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<dBus::RequestMessage<api::project::ProjectOperationEvent, void>>;
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

View File

@@ -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<RequirementItem>(m_requirementManager);
childItem->metadata = child.metadata;
childItem->details = child.details;
childItem->classification = child.classification;
const auto childItem = make_shared<RequirementItem>(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 */

View File

@@ -24,10 +24,16 @@ class RequirementItem : public std::enable_shared_from_this<RequirementItem>
explicit RequirementItem(RequirementManager &manager, // Constructor
const std::shared_ptr<RequirementItem> &parentRequirement = nullptr);
// Management functions
[[nodiscard]] Requirement toRequirement() const; // Convert this requirement item to an api::requirement::Requirement struct and return it
void update(const Requirement &updatedData); // Update the data of this requirement item with the provided updated requirement data
// Children management functions
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<RequirementItemPtr> 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<RequirementItemPtr> getChildren() const; // Get the vector of child requirement items of this requirement item
protected:
RequirementManager &m_requirementManager; // Reference to the requirement manager that owns this requirement item

View File

@@ -3,6 +3,7 @@
#include "bars/menuBar.h"
#include "bars/statusBar.h"
#include "bars/toolBar.h"
#include "requirementsPanel/requirementsPanel.h"
#include <filesystem>
@@ -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);

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
#pragma once
#include <dBus/dBus.h>
#include <wx/wx.h>
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<wxControl *> m_titleControls; // Titles controls for each section (metadata, details, classification)
private:
void createControls(); // Creating controls
wxSizer *createControls_metadata(wxWindow *owner); // Creating controls for requirement metadata
wxSizer *createControls_details(wxWindow *owner); // Creating controls for requirement details
wxSizer *createControls_classification(wxWindow *owner); // Creating controls for requirement classification
wxStaticText *createTitle(wxWindow *owner, const wxString &title); // Helper function to create a section title control
void updateTitleSizes() const; // Harmonize the sizes of the title controls to ensure they have the same width
};
//--------------------------------------------------------------
} // namespace gui

View File

@@ -0,0 +1,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()
{
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,28 @@
#pragma once
#include <dBus/dBus.h>
#include <wx/wx.h>
namespace gui
{
//--------------------------------------------------------------
class RequirementListPanel : public wxPanel
{
public:
RequirementListPanel() = delete; // Default constructor
virtual ~RequirementListPanel() = default; // Default destructor
RequirementListPanel(const RequirementListPanel &obj) = delete; // Copy constructor
RequirementListPanel(RequirementListPanel &&obj) noexcept = delete; // Move constructor
RequirementListPanel &operator=(const RequirementListPanel &obj) = delete; // Copy assignment operator
RequirementListPanel &operator=(RequirementListPanel &&obj) noexcept = delete; // Move assignment operator
explicit RequirementListPanel(wxWindow *parentWindow, dBus::Bus &bus); // Constructor
protected:
dBus::Bus &m_bus; // Reference to the application bus
private:
void createControls(); // Creating controls
};
//--------------------------------------------------------------
} // namespace gui

View File

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

View File

@@ -0,0 +1,68 @@
#pragma once
#include "gui/wxNode.h"
#include <dBus/dBus.h>
#include <wx/wx.h>
namespace gui
{
class RequirementDetailPanel;
class RequirementListPanel;
//--------------------------------------------------------------
class RequirementsPanel : public wxPanel
, public dBus::wxNode
{
public:
RequirementsPanel() = delete; // Default constructor
virtual ~RequirementsPanel() = default; // Default destructor
RequirementsPanel(const RequirementsPanel &obj) = delete; // Copy constructor
RequirementsPanel(RequirementsPanel &&obj) noexcept = delete; // Move constructor
RequirementsPanel &operator=(const RequirementsPanel &obj) = delete; // Copy assignment operator
RequirementsPanel &operator=(RequirementsPanel &&obj) noexcept = delete; // Move assignment operator
explicit RequirementsPanel(wxWindow *parentWindow, dBus::Bus &bus); // Constructor
protected:
// Controls
RequirementListPanel *m_requirementListPanel = nullptr; // Pointer to the requirement list panel control
RequirementDetailPanel *m_requirementDetailPanel = nullptr; // Pointer to the requirement detail panel control
private:
void createControls(); // Creating controls
// Events
void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) override; // dBus message reception handler
};
//--------------------------------------------------------------
} // namespace gui
/*
┌─────────────────────────────────────────────┐
│ ▼ Métadonnées │
│ ID: [REQ-001] 🔒 │
│ UUID: [abc-123-def] 🔒 │
│ Auteur: [John Doe] 🔒 │
│ Créé le: [2026-03-13 14:30] 🔒 │
│ Modifié le: [2026-03-13 15:45] 🔒 │
├─────────────────────────────────────────────┤
│ ▼ Détails │
│ Nom: [________________] │
│ Description: [ ] │
│ [ ] │
│ [________________] │
│ Critères: [ ] │
│ [________________] │
│ □ SMART │
├─────────────────────────────────────────────┤
│ ▼ Classification │
│ Type: [Functional ▼] │
│ Catégorie: [Backend ▼] │
│ Priorité: ● ● ● ○ ○ (3/5) │
│ Statut: ○ Draft ○ Review │
│ ● Approved ○ Rejected │
├─────────────────────────────────────────────┤
│ [Enregistrer] [Annuler] │
└─────────────────────────────────────────────┘
*/

View File

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