Implementation of a dBus management class for the GUI

This commit is contained in:
Sylvain Schneider
2026-03-12 18:57:35 +01:00
parent e64921702b
commit 16787ac642
4 changed files with 376 additions and 17 deletions

View File

@@ -4,7 +4,10 @@
#include "bars/statusBar.h"
#include "bars/toolBar.h"
#include <filesystem>
/* --- */
#include "api/project.h"
#include "api/requirement.h"
/* --- */
@@ -14,6 +17,7 @@ using namespace gui;
/* Constructor */
MainFrame::MainFrame(dBus::Bus &bus)
: wxFrame(nullptr, wxID_ANY, _("kwa.Fr"), wxDefaultPosition, wxDefaultSize)
, dBus::wxNode(this, bus)
, m_bus(bus)
{
// Initialization
@@ -27,9 +31,11 @@ MainFrame::MainFrame(dBus::Bus &bus)
createControls();
// Post-initialization
m_menuBar->Bind(wxEVT_MENU, &MainFrame::on_menubarItemClick, this);
m_menuBar->Bind(wxEVT_MENU, &MainFrame::on_menuBarItemClick, this);
CenterOnScreen();
subscribe(dBus::makeID("log.message"));
}
//--------------------------------------------------------------
/* Creating controls */
@@ -56,7 +62,7 @@ void MainFrame::createProject()
{
try
{
const auto ret = postFileOperationRequest(m_bus, api::requirement::FileOperation::Create);
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Create);
if (!ret)
{
wxMessageBox("No manager found for project creation request.\n\n"
@@ -80,25 +86,129 @@ void MainFrame::createProject()
/* Open an existing project */
void MainFrame::openProject()
{
try
{
wxFileDialog dlg(this,
_("Open Project"),
wxEmptyString,
wxEmptyString,
"Project Files (*.kwaProj)|*.kwaProj|All Files (*.*)|*.*",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() != wxID_OK)
return;
const auto filepath = filesystem::path(dlg.GetPath().ToStdString());
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Open, filepath);
if (!ret)
{
wxMessageBox("No manager found for project opening request.\n\n"
"The manager responsible for handling project opening "
"did not respond to the request. Please ensure that "
"the project management component is running and try again.",
"Failed...",
wxOK | wxICON_WARNING,
this);
}
}
catch (const std::exception &e)
{
wxMessageBox("An unexpected error occurred while opening a project\n\n" + wxString(e.what()),
"Critical Error",
wxOK | wxICON_ERROR,
this);
}
}
//--------------------------------------------------------------
/* Save the current project */
void MainFrame::saveProject()
{
try
{
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Save);
if (!ret)
{
wxMessageBox("No manager found for project saving request.\n\n"
"The manager responsible for handling project saving "
"did not respond to the request. Please ensure that "
"the project management component is running and try again.",
"Failed...",
wxOK | wxICON_WARNING,
this);
}
}
catch (const std::exception &e)
{
wxMessageBox("An unexpected error occurred while saving the project\n\n" + wxString(e.what()),
"Critical Error",
wxOK | wxICON_ERROR,
this);
}
}
//--------------------------------------------------------------
/* Save the current project as... */
void MainFrame::saveProjectAs()
{
try
{
wxFileDialog dlg(this,
_("Save Project"),
wxEmptyString,
wxEmptyString,
"Project Files (*.kwaProj)|*.kwaProj|All Files (*.*)|*.*",
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg.ShowModal() != wxID_OK)
return;
const auto filepath = filesystem::path(dlg.GetPath().ToStdString());
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::SaveAs, filepath);
if (!ret)
{
wxMessageBox("No manager found for project saving request.\n\n"
"The manager responsible for handling project saving "
"did not respond to the request. Please ensure that "
"the project management component is running and try again.",
"Failed...",
wxOK | wxICON_WARNING,
this);
}
}
catch (const std::exception &e)
{
wxMessageBox("An unexpected error occurred while saving the project\n\n" + wxString(e.what()),
"Critical Error",
wxOK | wxICON_ERROR,
this);
}
}
//--------------------------------------------------------------
/* Close the current project */
void MainFrame::closeProject() const
void MainFrame::closeProject()
{
try
{
const auto ret = api::project::postProjectOperationRequest(m_bus, api::project::OperationType::Close);
if (!ret)
{
wxMessageBox("No manager found for project closing request.\n\n"
"The manager responsible for handling project closing "
"did not respond to the request. Please ensure that "
"the project management component is running and try again.",
"Failed...",
wxOK | wxICON_WARNING,
this);
}
}
catch (const std::exception &e)
{
wxMessageBox("An unexpected error occurred while closing the project\n\n" + wxString(e.what()),
"Critical Error",
wxOK | wxICON_ERROR,
this);
}
}
//--------------------------------------------------------------
/* Menu bar item click event */
void MainFrame::on_menubarItemClick(wxCommandEvent &event)
void MainFrame::on_menuBarItemClick(wxCommandEvent &event)
{
switch (static_cast<MenuBar::IDs>(event.GetId()))
{
@@ -142,10 +252,15 @@ void MainFrame::on_menubarItemClick(wxCommandEvent &event)
}
// Help menu
default:
{
}
}
}
//--------------------------------------------------------------
/* dBus message reception handler */
void MainFrame::on_receiveMessageFromBus(const MessageID messageID, const MessagePtr message)
{
cout << "From MainFrame::on_receiveMessageFromBus: " << message->toString() << endl;
}
//--------------------------------------------------------------

View File

@@ -1,9 +1,10 @@
#pragma once
#include <memory>
#include "wxNode.h"
#include <dBus/dBus.h>
#include <wx/notebook.h>
#include <wx/wx.h>
#include <dBus/dBus.h>
namespace gui
{
@@ -12,6 +13,7 @@ class StatusBar;
class ToolBar;
//--------------------------------------------------------------
class MainFrame : public wxFrame
, public dBus::wxNode
{
public:
MainFrame() = delete; // Default constructor
@@ -38,11 +40,12 @@ class MainFrame : public wxFrame
void openProject(); // Open an existing project
void saveProject(); // Save the current project
void saveProjectAs(); // Save the current project as...
void closeProject() const; // Close the current project
void closeProject(); // Close the current project
// Events
public:
void on_menubarItemClick(wxCommandEvent &event); // Menu bar item click event
void on_menuBarItemClick(wxCommandEvent &event); // Menu bar item click event
void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) override; // dBus message reception handler
};
//--------------------------------------------------------------
} // namespace gui

159
src/gui/wxNode.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "wxNode.h"
wxDEFINE_EVENT(EVT_DBUS_EVENT_MESSAGE, wxDBusCommandEvent);
using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Constructor */
wxDBusCommandEvent::wxDBusCommandEvent(const dBus::HashID messageTypeID, MessagePtr message)
: wxCommandEvent(EVT_DBUS_EVENT_MESSAGE, 0)
, m_messageTypeID(messageTypeID)
, m_message(std::move(message))
{
// Nothing to do
}
//--------------------------------------------------------------
/* Clone method for wxWidgets event system */
wxEvent *wxDBusCommandEvent::Clone() const
{
auto *clonedEvent = new wxDBusCommandEvent(m_messageTypeID, m_message);
clonedEvent->SetEventObject(GetEventObject()); // Copy the event object
return clonedEvent;
}
//--------------------------------------------------------------
/* Return the message type ID */
HashID wxDBusCommandEvent::getMessageType() const
{
return m_messageTypeID;
}
//--------------------------------------------------------------
/* Set the message type ID */
void wxDBusCommandEvent::setDbusEventType(const dBus::HashID &messageTypeID)
{
m_messageTypeID = messageTypeID;
}
//--------------------------------------------------------------
/* Set the message type ID based on event type string */
void wxDBusCommandEvent::makeDbusEventType(const std::string &eventType)
{
m_messageTypeID = makeID(eventType);
}
//--------------------------------------------------------------
/* Check if the event has an associated message */
bool wxDBusCommandEvent::hasMessage() const
{
return m_message != nullptr;
}
//--------------------------------------------------------------
/* Get the associated message casted to a specific type */
wxDBusCommandEvent::MessagePtr wxDBusCommandEvent::getMessage() const
{
return m_message;
}
//--------------------------------------------------------------
/* Set the associated message for the event */
void wxDBusCommandEvent::setMessage(const MessagePtr &message)
{
m_message = message;
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/* Constructor */
dBus::wxNode::wxNode(wxWindow *owner, dBus::Bus &bus)
: Node(bus)
, m_owner(owner)
{
// Sanity check
if (!m_owner)
throw std::invalid_argument("Owner window cannot be null");
// Bind the custom event handler to the owner window
m_owner->Bind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);
// Start the event thread to listen for messages from the bus
startEventThread();
}
//--------------------------------------------------------------
/* Destructor */
dBus::wxNode::~wxNode()
{
// Stop the event thread to clean up resources
stopEventThread();
// Unbind the custom event handler from the owner window
if (m_owner)
m_owner->Unbind(EVT_DBUS_EVENT_MESSAGE, &wxNode::on_DBusEvent, this);
}
//--------------------------------------------------------------
/* Start the event thread to listen for messages from the bus */
void dBus::wxNode::startEventThread()
{
// Start thread to process events related to the channel.
// The thread will run until stop is requested.
// clang-format off
m_eventThread = std::jthread([this](const std::stop_token &stopToken)
{
// Process events until stop is requested
while (!stopToken.stop_requested())
{
eventThreadLoop();
}
});
// clang-format on
}
//--------------------------------------------------------------
/* Stop the event thread */
void dBus::wxNode::stopEventThread()
{
// Stop event thread loop
m_eventThread.request_stop();
// Wake up event thread if waiting
notifyMessageQueue();
// Ensure thread has fully stopped before unbinding
if (m_eventThread.joinable())
m_eventThread.join();
}
//--------------------------------------------------------------
/* The main loop of the event thread that listens for messages
* from the bus and posts events to the wxWidgets event system */
void dBus::wxNode::eventThreadLoop()
{
try
{
// Wait for a message to be received
syncWaitForMessage();
// Process all messages in the queue
while (getMessageCount() > 0)
{
const auto message = popNextMessage();
if (!message)
continue;
auto event = wxDBusCommandEvent(message->getMessageTypeID(), message);
wxQueueEvent(m_owner, event.Clone());
}
}
catch (const std::exception &e)
{
wxLogError("Exception in wxDBus event thread: %s", e.what());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
//--------------------------------------------------------------
/* Handler for wxDBusCommandEvent events posted to the GUI,
* extracts the message and calls on_receiveMessageFromBus */
void dBus::wxNode::on_DBusEvent(wxDBusCommandEvent &event)
{
const auto &messageID = event.getMessageType();
const auto &message = event.getMessage();
on_receiveMessageFromBus(messageID, message);
}
//--------------------------------------------------------------

82
src/gui/wxNode.h Normal file
View File

@@ -0,0 +1,82 @@
#pragma once
#include <dBus/dBus.h>
#include <thread>
#include <wx/wx.h>
//--------------------------------------------------------------
class wxDBusCommandEvent : public wxCommandEvent
{
using MessagePtr = std::shared_ptr<dBus::Message>;
public:
wxDBusCommandEvent() = delete; // Default constructor
virtual ~wxDBusCommandEvent() = default; // Default destructor
wxDBusCommandEvent(const wxDBusCommandEvent &obj) = delete; // Copy constructor
wxDBusCommandEvent(wxDBusCommandEvent &&obj) noexcept = delete; // Move constructor
wxDBusCommandEvent &operator=(const wxDBusCommandEvent &obj) = delete; // Copy assignment operator
wxDBusCommandEvent &operator=(wxDBusCommandEvent &&obj) noexcept = delete; // Move assignment operator
explicit wxDBusCommandEvent(dBus::HashID messageTypeID, // Constructor
MessagePtr message = {});
[[nodiscard]] wxEvent *Clone() const override; // Clone method for wxWidgets event system
// dBus message type management
[[nodiscard]] dBus::HashID getMessageType() const; // Return the message type ID
void setDbusEventType(const dBus::HashID &messageTypeID); // Set the message type ID
void makeDbusEventType(const std::string &eventType); // Set the message type ID based on event type string
// dBus message payload management
[[nodiscard]] bool hasMessage() const; // Check if the event has an associated message
[[nodiscard]] MessagePtr getMessage() const; // Get the associated message casted to a specific type
void setMessage(const MessagePtr &message); // Set the associated message for the event
protected:
dBus::HashID m_messageTypeID; // Message type ID
MessagePtr m_message; // Store the original dBus message
};
//--------------------------------------------------------------
wxDECLARE_EVENT(EVT_DBUS_EVENT_MESSAGE, wxDBusCommandEvent);
/* --- */
namespace dBus
{
//--------------------------------------------------------------
class wxNode : private dBus::Node
{
public:
using MessageID = dBus::HashID;
using MessagePtr = std::shared_ptr<dBus::Message>;
public:
wxNode() = delete; // Default constructor
virtual ~wxNode(); // Default destructor
wxNode(const wxNode &obj) = delete; // Copy constructor
wxNode(wxNode &&obj) noexcept = delete; // Move constructor
wxNode &operator=(const wxNode &obj) = delete; // Copy assignment operator
wxNode &operator=(wxNode &&obj) noexcept = delete; // Move assignment operator
explicit wxNode(wxWindow *owner, dBus::Bus &bus); // Constructor
using Node::subscribe; // Subscribe to a specific message type
using Node::unsubscribe; // Unsubscribe from a specific message type
// Message reception handler to be overridden by the gui
// derived class to handle incoming messages from the bus
virtual void on_receiveMessageFromBus(MessageID messageID, MessagePtr message) = 0;
protected:
wxWindow *m_owner = nullptr; // Pointer to the owner window (for posting events to the GUI)
std::jthread m_eventThread; // Thread for listening to messages from the bus
private:
void startEventThread(); // Start the event thread to listen for messages from the bus
void stopEventThread(); // Stop the event thread
void eventThreadLoop(); // Main loop event thread function
void on_DBusEvent(wxDBusCommandEvent &event); // Handler for wxDBusCommandEvent events
};
//--------------------------------------------------------------
} // namespace dBus