Work on the graphical interface requirements tree

This commit is contained in:
Sylvain Schneider
2026-03-17 17:11:02 +01:00
parent 806aeb4824
commit 89f277d013
10 changed files with 237 additions and 29 deletions

View File

@@ -28,7 +28,7 @@ struct Metadata
//-------------------------------------------------------------- //--------------------------------------------------------------
struct Details struct Details
{ {
std::string title; // 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 description; // Detailed description of the requirement
std::string acceptance_criteria; // Criteria that must be met for the requirement to be considered complete 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) bool is_smart; // SMART indicator (Specific, Measurable, Achievable, Relevant, Time-bound)
@@ -65,7 +65,7 @@ struct GetRequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.ge
} }
}; };
//-------------------------------------------------------------- //--------------------------------------------------------------
inline std::expected<Requirement, dBus::ReturnStatus> postGetRequirementRequest(dBus::Bus &bus, const std::string &uuid) inline std::expected<Requirement, dBus::ReturnStatus> postGetRequirementRequest(dBus::Bus &bus, const std::string &uuid = "")
{ {
auto eventData = GetRequirementEvent(); auto eventData = GetRequirementEvent();
eventData.uuid = std::move(uuid); eventData.uuid = std::move(uuid);

View File

@@ -10,7 +10,8 @@ using namespace core::project;
/* Default constructor */ /* Default constructor */
RequirementManager::RequirementManager() RequirementManager::RequirementManager()
{ {
m_rootRequirement = make_shared<RequirementItem>(*this); // Create the root requirement item and set it as the root requirement m_rootRequirement = make_shared<RequirementItem>(*this); // Create the root requirement item and set it as the root requirement
m_requirementsByUuid[m_rootRequirement->metadata.uuid] = m_rootRequirement; // Register the root requirement item
} }
//-------------------------------------------------------------- //--------------------------------------------------------------
/* Get the root requirement */ /* Get the root requirement */
@@ -28,9 +29,13 @@ std::vector<RequirementManager::RequirementItemPtr> RequirementManager::getRootR
/* Get a requirement by its UUID */ /* Get a requirement by its UUID */
RequirementManager::RequirementItemPtr RequirementManager::getRequirement(const std::string &uuid) const RequirementManager::RequirementItemPtr RequirementManager::getRequirement(const std::string &uuid) const
{ {
if (uuid.empty())
return m_rootRequirement; // Return the root requirement item pointer if the UUID is empty
if (m_requirementsByUuid.contains(uuid)) if (m_requirementsByUuid.contains(uuid))
return m_requirementsByUuid.at(uuid); // Return the requirement item pointer if found in the map return m_requirementsByUuid.at(uuid); // Return the requirement item pointer if found in the map
return {}; // Return nullptr if the requirement with the given UUID is not found
return {}; // Return nullptr if the requirement with the given UUID is not found
} }
//-------------------------------------------------------------- //--------------------------------------------------------------
/* Get the vector of child requirement items of a requirement by its UUID */ /* Get the vector of child requirement items of a requirement by its UUID */

View File

@@ -14,7 +14,6 @@ RequirementItem::RequirementItem(RequirementManager &manager, const std::shared_
m_parent = parentRequirement; // Set the m_parent requirement item m_parent = parentRequirement; // Set the m_parent requirement item
// Register this requirement item in the manager's internal map // Register this requirement item in the manager's internal map
m_requirementManager.registerRequirement(shared_from_this());
} }
//-------------------------------------------------------------- //--------------------------------------------------------------
/* Convert this requirement item to an api::requirement::Requirement struct and return it */ /* Convert this requirement item to an api::requirement::Requirement struct and return it */
@@ -52,6 +51,8 @@ void RequirementItem::appendChild(const Requirement &child)
{ {
// Create a new requirement item for the child requirement and set this requirement item as its m_parent // Create a new requirement item for the child requirement and set this requirement item as its m_parent
const auto childItem = make_shared<RequirementItem>(m_requirementManager, shared_from_this()); const auto childItem = make_shared<RequirementItem>(m_requirementManager, shared_from_this());
m_requirementManager.registerRequirement(childItem); // Register the child requirement item
childItem->update(child); // Update the child requirement item with the provided child requirement data childItem->update(child); // Update the child requirement item with the provided child requirement data
// Append the child requirement item to the vector of children // Append the child requirement item to the vector of children

View File

@@ -45,13 +45,16 @@ MainFrame::MainFrame(dBus::Bus &bus)
/* Creating controls */ /* Creating controls */
void MainFrame::createControls() void MainFrame::createControls()
{ {
m_menuBar = new MenuBar(this); m_menuBar = new MenuBar(this);
m_statusBar = new StatusBar(this); m_statusBar = new StatusBar(this);
const auto framePanel = new wxPanel(this /*, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER*/); const auto framePanel = new wxPanel(this /*, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER*/);
m_toolBar = new ToolBar(framePanel); 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_requirementsPanel = new RequirementsPanel(m_pageNotebook, m_bus);
m_requirementsPanel->SetBackgroundColour(framePanel->GetBackgroundColour());
m_pageNotebook->AddPage(m_requirementsPanel, "Requirements", true); m_pageNotebook->AddPage(m_requirementsPanel, "Requirements", true);

View File

@@ -1,5 +1,7 @@
#include "requirementDetailPanel.h" #include "requirementDetailPanel.h"
#include "resources/resources.h"
#include <wx/statline.h> #include <wx/statline.h>
using namespace std; using namespace std;
@@ -24,9 +26,9 @@ void RequirementDetailPanel::createControls()
const auto mainPanel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL); const auto mainPanel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
mainPanel->SetScrollRate(0, 10); // Set the scroll rate for the panel (vertical only) mainPanel->SetScrollRate(0, 10); // Set the scroll rate for the panel (vertical only)
const auto closeButton = new wxButton(this, wxID_ANY, _("Close")); const auto closeButton = new wxBitmapButton(this, wxID_ANY, icon_xmark_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
const auto saveButton = new wxButton(this, wxID_ANY, _("Save")); const auto saveButton = new wxBitmapButton(this, wxID_ANY, icon_floppy_disk_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
const auto discardButton = new wxButton(this, wxID_ANY, _("Discard")); const auto discardButton = new wxBitmapButton(this, wxID_ANY, icon_rotate_left_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
// Controls positioning // Controls positioning
const auto ctrlSizer = new wxBoxSizer(wxVERTICAL); const auto ctrlSizer = new wxBoxSizer(wxVERTICAL);

View File

@@ -1,5 +1,8 @@
#include "requirementListPanel.h" #include "requirementListPanel.h"
#include "requirementTreeModel.h"
#include "resources/resources.h"
using namespace std; using namespace std;
using namespace gui; using namespace gui;
//-------------------------------------------------------------- //--------------------------------------------------------------
@@ -19,5 +22,47 @@ RequirementListPanel::RequirementListPanel(wxWindow *parentWindow, dBus::Bus &bu
/* Creating controls */ /* Creating controls */
void RequirementListPanel::createControls() void RequirementListPanel::createControls()
{ {
// Create the data view control
m_dataViewCtrl = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
m_dataViewCtrl->AppendTextColumn(_T("ID"), 0, wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT);
m_dataViewCtrl->AppendTextColumn(_("Title"), 1, wxDATAVIEW_CELL_INERT, 200, wxALIGN_LEFT);
const auto model = new RequirementTreeModel(m_bus);
model->loadRootRequirement();
m_dataViewCtrl->AssociateModel(model);
model->DecRef(); // The control now owns the model, so we can release our reference
// Create other controls
const auto addButton = new wxBitmapButton(this, wxID_ANY, icon_plus_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
addButton->Bind(wxEVT_BUTTON, &RequirementListPanel::on_addButtonClick, this);
const auto reloadButton = new wxBitmapButton(this, wxID_ANY, icon_rotate_left_24x24(), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW | wxBORDER_NONE);
reloadButton->Bind(wxEVT_BUTTON, &RequirementListPanel::on_reloadButtonClick, this);
// Controls positioning
const auto buttonSizer = new wxBoxSizer(wxHORIZONTAL);
buttonSizer->Add(addButton, wxSizerFlags(0).Border(wxALL, 5));
buttonSizer->AddStretchSpacer();
buttonSizer->Add(reloadButton, wxSizerFlags(0).Border(wxALL, 5));
const auto mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_dataViewCtrl, wxSizerFlags(1).Expand().Border(wxALL, 5));
mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand());
SetSizer(mainSizer);
}
//--------------------------------------------------------------
/* Process the add requirement click event */
void RequirementListPanel::on_addButtonClick(wxCommandEvent &event)
{
}
//--------------------------------------------------------------
/* Process the reload requirements click event */
void RequirementListPanel::on_reloadButtonClick(wxCommandEvent &event)
{
// Reload all requirements
if (auto *model = dynamic_cast<RequirementTreeModel *>(m_dataViewCtrl->GetModel()))
model->loadRootRequirement();
} }
//-------------------------------------------------------------- //--------------------------------------------------------------

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <dBus/dBus.h> #include <dBus/dBus.h>
#include <memory>
#include <wx/dataview.h>
#include <wx/wx.h> #include <wx/wx.h>
namespace gui namespace gui
@@ -9,8 +11,8 @@ namespace gui
class RequirementListPanel : public wxPanel class RequirementListPanel : public wxPanel
{ {
public: public:
RequirementListPanel() = delete; // Default constructor RequirementListPanel() = delete; // Default constructor
virtual ~RequirementListPanel() = default; // Default destructor virtual ~RequirementListPanel() = default; // Default destructor
RequirementListPanel(const RequirementListPanel &obj) = delete; // Copy constructor RequirementListPanel(const RequirementListPanel &obj) = delete; // Copy constructor
RequirementListPanel(RequirementListPanel &&obj) noexcept = delete; // Move constructor RequirementListPanel(RequirementListPanel &&obj) noexcept = delete; // Move constructor
RequirementListPanel &operator=(const RequirementListPanel &obj) = delete; // Copy assignment operator RequirementListPanel &operator=(const RequirementListPanel &obj) = delete; // Copy assignment operator
@@ -21,8 +23,15 @@ class RequirementListPanel : public wxPanel
protected: protected:
dBus::Bus &m_bus; // Reference to the application bus dBus::Bus &m_bus; // Reference to the application bus
// Controls
wxDataViewCtrl *m_dataViewCtrl = nullptr;
private: private:
void createControls(); // Creating controls void createControls(); // Creating controls
// Events
void on_addButtonClick(wxCommandEvent &event); // Process the add requirement click event
void on_reloadButtonClick(wxCommandEvent &event); // Process the reload requirements click event
}; };
//-------------------------------------------------------------- //--------------------------------------------------------------
} // namespace gui } // namespace gui

View File

@@ -0,0 +1,133 @@
#include "requirementTreeModel.h"
using namespace std;
using namespace gui;
//--------------------------------------------------------------
/* Constructor */
RequirementTreeModel::RequirementTreeModel(dBus::Bus &bus)
: m_bus(bus)
{
}
//--------------------------------------------------------------
void RequirementTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, const unsigned col) const
{
// Sanity check
if (!item.IsOk())
return;
const auto *data = static_cast<RequirementData *>(item.GetID());
if (!data)
return;
// Return the appropriate value based on the column index
switch (col)
{
case 0: // ID column
variant = data->id;
break;
case 1: // Title column
variant = data->title;
break;
default:
break;
}
}
//--------------------------------------------------------------
bool RequirementTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned col)
{
return {};
}
//--------------------------------------------------------------
wxDataViewItem RequirementTreeModel::GetParent(const wxDataViewItem &item) const
{
if (!item.IsOk())
return wxDataViewItem(nullptr);
// Get the requirement data associated with the item
const auto *data = static_cast<RequirementData *>(item.GetID());
if (!data)
return wxDataViewItem(nullptr); // If the item is invalid, return an invalid item
if (data == &m_rootRequirement)
return wxDataViewItem(nullptr); // If the item is the root requirement, return an invalid item
if (!data->parent)
return wxDataViewItem(nullptr); // If the item has no parent, return an invalid item
return wxDataViewItem(data->parent);
}
//--------------------------------------------------------------
bool RequirementTreeModel::IsContainer(const wxDataViewItem &item) const
{
const auto *data = static_cast<RequirementData *>(item.GetID());
if (!data)
return true;
return !data->children.empty(); // An item is a container if it has children
}
//--------------------------------------------------------------
bool RequirementTreeModel::HasContainerColumns(const wxDataViewItem &wxDataViewItem) const
{
return true;
}
//--------------------------------------------------------------
unsigned int RequirementTreeModel::GetChildren(const wxDataViewItem &item, wxDataViewItemArray &children) const
{
const auto *data = static_cast<RequirementData *>(item.GetID());
if (!data) // If the item is invalid, return the root requirement as the only child
{
children.Add(wxDataViewItem(const_cast<RequirementData *>(&m_rootRequirement)));
return 1;
}
// Add the children of the current item to the children array
for (const auto &child : data->children)
{
children.Add(wxDataViewItem(child.get()));
}
return static_cast<uint32_t>(data->children.size());
}
//--------------------------------------------------------------
void RequirementTreeModel::loadRootRequirement()
{
// Get the root requirements from dBus
try
{
const auto ret = api::requirement::postGetRequirementRequest(m_bus);
if (!ret)
{
wxMessageBox("No manager found for requirement request.\n\n"
"Error loading root requirements.",
"Error",
wxOK | wxICON_ERROR);
return;
}
m_rootRequirement = RequirementData(*ret);
}
catch (const std::exception &e)
{
// Handle the error (e.g., log it, show a message to the user, etc.)
// For now, we just print the error message to the console
std::cerr << "Error loading root requirements: " << e.what() << std::endl;
return;
}
// Notify the control that the data has changed
//ItemChanged(wxDataViewItem(nullptr)); // Notify that the entire tree has changed
//ItemsAdded(wxDataViewItem(nullptr), wxDataViewItemArray{ wxDataViewItem(&m_rootRequirement) });
Cleared();
}
//--------------------------------------------------------------
RequirementData *RequirementTreeModel::AddChildRequirement(RequirementData *parent, std::unique_ptr<RequirementData> requirementData)
{
return {};
}
//--------------------------------------------------------------
void RequirementTreeModel::clear()
{
}
//--------------------------------------------------------------
RequirementData *RequirementTreeModel::GetRequirementData(const wxDataViewItem &item) const
{
return {};
}
//--------------------------------------------------------------

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <api/requirement.h>
#include <dBus/dBus.h>
#include <filesystem> #include <filesystem>
#include <wx/dataview.h> #include <wx/dataview.h>
#include <wx/wx.h> #include <wx/wx.h>
@@ -9,18 +11,21 @@ namespace gui
//-------------------------------------------------------------- //--------------------------------------------------------------
struct RequirementData struct RequirementData
{ {
std::string uuid;
std::string id; std::string id;
std::string title; std::string title;
RequirementData *parent; RequirementData *parent = nullptr;
std::vector<std::unique_ptr<RequirementData>> children; std::vector<std::unique_ptr<RequirementData>> children;
RequirementData(std::string uuid_, std::string id_, std::string title_) RequirementData()
: uuid(std::move(uuid_))
, id(std::move(id_))
, title(std::move(title_))
{ {
id = "badID";
title = "badTITLE";
}
RequirementData(const api::requirement::Requirement &requirement)
{
id = "testID";
title = "testTITLE";
} }
// Adds a child requirement // Adds a child requirement
@@ -39,34 +44,37 @@ struct RequirementData
class RequirementTreeModel : public wxDataViewModel class RequirementTreeModel : public wxDataViewModel
{ {
public: public:
RequirementTreeModel(); // Default constructor RequirementTreeModel() = delete; // Default constructor
virtual ~RequirementTreeModel() = default; // Default destructor virtual ~RequirementTreeModel() = default; // Default destructor
RequirementTreeModel(const RequirementTreeModel &obj) = delete; // Copy constructor RequirementTreeModel(const RequirementTreeModel &obj) = delete; // Copy constructor
RequirementTreeModel(RequirementTreeModel &&obj) noexcept = delete; // Move constructor RequirementTreeModel(RequirementTreeModel &&obj) noexcept = delete; // Move constructor
RequirementTreeModel &operator=(const RequirementTreeModel &obj) = delete; // Copy assignment operator RequirementTreeModel &operator=(const RequirementTreeModel &obj) = delete; // Copy assignment operator
RequirementTreeModel &operator=(RequirementTreeModel &&obj) noexcept = delete; // Move assignment operator RequirementTreeModel &operator=(RequirementTreeModel &&obj) noexcept = delete; // Move assignment operator
// wxDataViewModel overrides methods explicit RequirementTreeModel(dBus::Bus &bus); // Constructor
unsigned GetColumnCount() const override;
wxString GetColumnType(unsigned) const override;
// wxDataViewModel overrides methods
void GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned col) const override; void GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned col) const override;
bool SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned col) override; bool SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned col) override;
wxDataViewItem GetParent(const wxDataViewItem &item) const override; [[nodiscard]] wxDataViewItem GetParent(const wxDataViewItem &item) const override;
bool IsContainer(const wxDataViewItem &item) const override; [[nodiscard]] bool IsContainer(const wxDataViewItem &item) const override;
bool HasContainerColumns(const wxDataViewItem &) const override; [[nodiscard]] bool HasContainerColumns(const wxDataViewItem &) const override;
unsigned GetChildren(const wxDataViewItem &item, wxDataViewItemArray &children) const override; unsigned int GetChildren(const wxDataViewItem &item, wxDataViewItemArray &children) const override;
// Tree data model specific methods // Tree data model specific methods
RequirementData *AddRootRequirement(std::unique_ptr<RequirementData> requirementData); // Add a root requirement to the tree void loadRootRequirement(); // Add a root requirement to the tree
RequirementData *AddChildRequirement(RequirementData *parent, std::unique_ptr<RequirementData> requirementData); // Add a child requirement to a parent requirement RequirementData *AddChildRequirement(RequirementData *parent, std::unique_ptr<RequirementData> requirementData); // Add a child requirement to a parent requirement
void clear(); void clear();
RequirementData *GetRequirementData(const wxDataViewItem &item) const; // Get the requirement data associated with a given item RequirementData *GetRequirementData(const wxDataViewItem &item) const; // Get the requirement data associated with a given item
protected: protected:
std::vector<std::unique_ptr<RequirementData>> m_rootRequirement; dBus::Bus &m_bus; // Reference to the application bus
// std::vector<std::unique_ptr<RequirementData>> m_rootRequirement;
RequirementData m_rootRequirement;
}; };
//-------------------------------------------------------------- //--------------------------------------------------------------
} // namespace gui } // namespace gui

View File

@@ -12,6 +12,8 @@ RequirementsPanel::RequirementsPanel(wxWindow *parentWindow, dBus::Bus &bus)
, dBus::wxNode(this, bus) , dBus::wxNode(this, bus)
{ {
// Initialization // Initialization
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
SetOwnBackgroundColour(GetBackgroundColour());
// Creating controls // Creating controls
createControls(); createControls();