diff --git a/src/gui/mainFrame.cpp b/src/gui/mainFrame.cpp index 72c0bb3..a3272c5 100644 --- a/src/gui/mainFrame.cpp +++ b/src/gui/mainFrame.cpp @@ -4,7 +4,10 @@ #include "bars/statusBar.h" #include "bars/toolBar.h" +#include + /* --- */ +#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,30 +86,134 @@ 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(event.GetId())) { using enum MenuBar::IDs; - // File menu + // File menu case ID_MENU_FILE_NEW: { createProject(); @@ -135,17 +245,22 @@ void MainFrame::on_menubarItemClick(wxCommandEvent &event) break; } - // Requirement menu + // Requirement menu case ID_MENU_REQ_CREATE: { break; } - // Help menu - + // 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; +} +//-------------------------------------------------------------- diff --git a/src/gui/mainFrame.h b/src/gui/mainFrame.h index 18c1edd..c16ac8a 100644 --- a/src/gui/mainFrame.h +++ b/src/gui/mainFrame.h @@ -1,9 +1,10 @@ #pragma once -#include +#include "wxNode.h" + +#include #include #include -#include namespace gui { @@ -12,6 +13,7 @@ class StatusBar; class ToolBar; //-------------------------------------------------------------- class MainFrame : public wxFrame + , public dBus::wxNode { public: MainFrame() = delete; // Default constructor @@ -28,21 +30,22 @@ class MainFrame : public wxFrame MenuBar *m_menuBar = nullptr; // Menu bar handle StatusBar *m_statusBar = nullptr; // Status bar handle - ToolBar *m_toolBar = nullptr; // Tool bar handle + ToolBar *m_toolBar = nullptr; // Toolbar handle wxNotebook *m_pageNotebook = nullptr; // Main notebook handle private: void createControls(); // Creating controls - void createProject(); // Create a new project - 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 createProject(); // Create a new project + void openProject(); // Open an existing project + void saveProject(); // Save the current project + void saveProjectAs(); // Save the current project as... + 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 diff --git a/src/gui/wxNode.cpp b/src/gui/wxNode.cpp new file mode 100644 index 0000000..0cbb5b9 --- /dev/null +++ b/src/gui/wxNode.cpp @@ -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); +} +//-------------------------------------------------------------- diff --git a/src/gui/wxNode.h b/src/gui/wxNode.h new file mode 100644 index 0000000..87fb3d4 --- /dev/null +++ b/src/gui/wxNode.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +//-------------------------------------------------------------- +class wxDBusCommandEvent : public wxCommandEvent +{ + using MessagePtr = std::shared_ptr; + + 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; + + 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 \ No newline at end of file