Add a wxWidgets GUI frontend (second frontend, proves the split).
A native wx GUI built entirely on essim_core via the Frontend interface — no
Tui reuse, no command shell. Demonstrates the core/frontends architecture by
adding a real second frontend:
- WxFrontend : public Frontend — owns the System + a console buffer; handles
boot headlessly (restore for --restore/--batch; honest note for source);
Run() boots wx without a generated main (SetInstance + wxEntryStart/
CallOnInit/OnRun) so the shared frontend_main stays in control.
- EssimFrame (wxFrame) — menu-driven window: Load (app::load_module), Restore/
Save (persist), Export (app::export_connections), Verify (app::verify),
rendered into a model tree (modules → parts), an overview + verify-health
panel, and a log. Each handler is a thin wrapper over a core/app op.
- main.cpp: construct WxFrontend, call frontend_main — same 4 lines as tui.
- CMakeLists.txt: find_package(wxWidgets) + essim_add_frontend(wx LIBS …);
select with -DESSIM_FRONTEND=wx. ESSIM_FRONTEND gains wx in its STRINGS.
Set LC_CTYPE from the environment at GUI init so wxString decodes the UTF-8 in
narrow literals (em dash, ellipsis); LC_NUMERIC stays "C". .gitignore: build*/.
Needs libwxgtk3.2-dev. Verified: builds clean (wx 3.2.9 GTK3), window opens and
renders with no wx asserts; --commands-md/--restore/--batch behave headlessly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1 @@
|
||||
build/
|
||||
build*/
|
||||
|
||||
@@ -48,7 +48,7 @@ target_link_libraries(essim_core
|
||||
# src/frontends/gui/ and configure with -DESSIM_FRONTEND=gui.
|
||||
set(ESSIM_FRONTEND "tui" CACHE STRING
|
||||
"Frontend to build: a directory name under src/frontends/, or 'none'")
|
||||
set_property(CACHE ESSIM_FRONTEND PROPERTY STRINGS tui none)
|
||||
set_property(CACHE ESSIM_FRONTEND PROPERTY STRINGS tui wx none)
|
||||
|
||||
if(ESSIM_FRONTEND STREQUAL "none")
|
||||
message(STATUS "essim: ESSIM_FRONTEND=none — core + tests only (no frontend, no GUI toolkit)")
|
||||
|
||||
16
src/frontends/wx/CMakeLists.txt
Normal file
16
src/frontends/wx/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# wxWidgets GUI frontend. Builds the `essim` executable against essim_core.
|
||||
#
|
||||
# Self-contained like every frontend: it pulls its own GUI toolkit (here a
|
||||
# system wxWidgets via find_package), then defers the target wiring to the
|
||||
# shared essim_add_frontend() helper. Select it with -DESSIM_FRONTEND=wx.
|
||||
#
|
||||
# Needs the wxWidgets development package, e.g. on Debian/Ubuntu:
|
||||
# sudo apt install libwxgtk3.2-dev
|
||||
|
||||
find_package(wxWidgets REQUIRED COMPONENTS core base)
|
||||
|
||||
# UsewxWidgets sets the include dirs and compile definitions for targets defined
|
||||
# afterwards in this directory — so it must come before essim_add_frontend().
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
essim_add_frontend(wx LIBS ${wxWidgets_LIBRARIES})
|
||||
10
src/frontends/wx/main.cpp
Normal file
10
src/frontends/wx/main.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "frontends/frontend_main.hpp"
|
||||
#include "frontends/wx/wx_frontend.hpp"
|
||||
|
||||
// The wx frontend's entry point: construct the concrete Frontend (WxFrontend)
|
||||
// and hand off to the shared, frontend-agnostic launcher. Identical in shape to
|
||||
// the tui frontend's main — only the Frontend type differs.
|
||||
int main(int argc, char **argv) {
|
||||
WxFrontend fe;
|
||||
return frontend_main(argc, argv, fe);
|
||||
}
|
||||
285
src/frontends/wx/wx_frame.cpp
Normal file
285
src/frontends/wx/wx_frame.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#include "frontends/wx/wx_frame.hpp"
|
||||
|
||||
#include "frontends/wx/wx_frontend.hpp"
|
||||
|
||||
#include "core/app/export.hpp"
|
||||
#include "core/app/load.hpp"
|
||||
#include "core/app/verify.hpp"
|
||||
#include "core/domain/analysis.hpp"
|
||||
#include "core/domain/connect.hpp"
|
||||
#include "core/domain/modules.hpp"
|
||||
#include "core/domain/parts.hpp"
|
||||
#include "core/domain/persist.hpp"
|
||||
#include "core/domain/signal_type.hpp"
|
||||
#include "core/domain/signals.hpp"
|
||||
#include "core/domain/system.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/choicdlg.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/textdlg.h>
|
||||
#include <wx/treectrl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
enum {
|
||||
ID_LOAD = wxID_HIGHEST + 1,
|
||||
ID_RESTORE,
|
||||
ID_SAVE,
|
||||
ID_EXPORT,
|
||||
ID_VERIFY,
|
||||
ID_QUIT,
|
||||
ID_ABOUT,
|
||||
};
|
||||
|
||||
// Core (UTF-8 std::string) -> wxString, and back for paths.
|
||||
inline wxString wx(const std::string &s) { return wxString::FromUTF8(s.c_str()); }
|
||||
} // namespace
|
||||
|
||||
EssimFrame::EssimFrame(WxFrontend &fe)
|
||||
: wxFrame(nullptr, wxID_ANY, "essim — system digital twin",
|
||||
wxDefaultPosition, wxSize(960, 640)),
|
||||
fe_(fe) {
|
||||
auto *file = new wxMenu;
|
||||
file->Append(ID_LOAD, "&Load module…\tCtrl-L");
|
||||
file->Append(ID_RESTORE, "&Restore snapshot…\tCtrl-R");
|
||||
file->Append(ID_SAVE, "&Save snapshot…\tCtrl-S");
|
||||
file->AppendSeparator();
|
||||
file->Append(ID_EXPORT, "&Export connections…\tCtrl-E");
|
||||
file->AppendSeparator();
|
||||
file->Append(ID_QUIT, "&Quit\tCtrl-Q");
|
||||
|
||||
auto *sysm = new wxMenu;
|
||||
sysm->Append(ID_VERIFY, "&Verify\tCtrl-K");
|
||||
|
||||
auto *help = new wxMenu;
|
||||
help->Append(ID_ABOUT, "&About");
|
||||
|
||||
auto *bar = new wxMenuBar;
|
||||
bar->Append(file, "&File");
|
||||
bar->Append(sysm, "&System");
|
||||
bar->Append(help, "&Help");
|
||||
SetMenuBar(bar);
|
||||
|
||||
CreateStatusBar();
|
||||
SetStatusText("essim — wx frontend");
|
||||
|
||||
auto *panel = new wxPanel(this);
|
||||
tree_ = new wxTreeCtrl(panel, wxID_ANY);
|
||||
overview_ = new wxTextCtrl(panel, wxID_ANY, "", wxDefaultPosition,
|
||||
wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
log_ = new wxTextCtrl(panel, wxID_ANY, "", wxDefaultPosition,
|
||||
wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
|
||||
wxFont mono(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
|
||||
overview_->SetFont(mono);
|
||||
log_->SetFont(mono);
|
||||
|
||||
auto *top = new wxBoxSizer(wxHORIZONTAL);
|
||||
top->Add(tree_, 1, wxEXPAND | wxALL, 4);
|
||||
top->Add(overview_, 1, wxEXPAND | wxALL, 4);
|
||||
|
||||
auto *root = new wxBoxSizer(wxVERTICAL);
|
||||
root->Add(top, 2, wxEXPAND);
|
||||
root->Add(new wxStaticText(panel, wxID_ANY, " Log"), 0, wxLEFT | wxTOP, 6);
|
||||
root->Add(log_, 1, wxEXPAND | wxALL, 4);
|
||||
panel->SetSizer(root);
|
||||
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnLoad, this, ID_LOAD);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnRestore, this, ID_RESTORE);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnSave, this, ID_SAVE);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnVerify, this, ID_VERIFY);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
|
||||
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
||||
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::Log(const wxString &line) {
|
||||
log_->AppendText(line + "\n");
|
||||
}
|
||||
|
||||
void EssimFrame::RebuildModelView() {
|
||||
System *sys = fe_.system();
|
||||
|
||||
tree_->DeleteAllItems();
|
||||
wxTreeItemId root = tree_->AddRoot("System");
|
||||
|
||||
int n_mods = 0, n_parts = 0, n_sigs = 0;
|
||||
if (sys) {
|
||||
std::vector<std::string> mods;
|
||||
for (auto &mkv : *sys->modules()) mods.push_back(mkv.first);
|
||||
std::sort(mods.begin(), mods.end());
|
||||
n_mods = (int)mods.size();
|
||||
for (const auto &mname : mods) {
|
||||
Module *m = sys->modules()->get(mname);
|
||||
int mp = (int)m->size();
|
||||
int ms = (int)m->signals->size();
|
||||
n_parts += mp;
|
||||
n_sigs += ms;
|
||||
wxTreeItemId mid = tree_->AppendItem(
|
||||
root, wx(mname) + wxString::Format(" — %d part(s), %d signal(s)",
|
||||
mp, ms));
|
||||
std::vector<std::string> parts;
|
||||
for (auto &pkv : *m) parts.push_back(pkv.first);
|
||||
std::sort(parts.begin(), parts.end());
|
||||
for (const auto &pname : parts) {
|
||||
Part *p = m->get(pname);
|
||||
wxString label = wx(pname)
|
||||
+ wxString::Format(" (%d pin(s))", (int)p->size());
|
||||
if (!p->connector_type.empty())
|
||||
label += " [" + wx(p->connector_type) + "]";
|
||||
tree_->AppendItem(mid, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
tree_->Expand(root);
|
||||
|
||||
int n_conn = sys ? (int)sys->connections()->size() : 0;
|
||||
|
||||
wxString ov;
|
||||
ov << "Modules: " << n_mods << "\n"
|
||||
<< "Parts: " << n_parts << "\n"
|
||||
<< "Signals: " << n_sigs << "\n"
|
||||
<< "Connections: " << n_conn << "\n";
|
||||
if (sys) {
|
||||
app::VerifyReport r = app::verify(sys);
|
||||
ov << "\nHealth (verify):\n"
|
||||
<< wxString::Format(" pin-role mismatches: %d / %d typed pin(s)\n",
|
||||
(int)r.role_mismatches.size(), r.typed_pins)
|
||||
<< wxString::Format(" net inconsistencies: %d / %d bridged net(s)\n",
|
||||
(int)r.net_inconsistencies.size(), r.bridged_nets)
|
||||
<< wxString::Format(" orphan pins: %d (%d imported, %d dropped)\n",
|
||||
r.orphan_total(), r.orphan_imported, r.orphan_dropped)
|
||||
<< wxString::Format(" model anomalies: %d\n", r.model_total());
|
||||
}
|
||||
overview_->SetValue(ov);
|
||||
}
|
||||
|
||||
void EssimFrame::OnLoad(wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Load a netlist / pinout file", "", "",
|
||||
"All files (*.*)|*.*", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
const wxString path = dlg.GetPath();
|
||||
|
||||
wxString modname = wxGetTextFromUser("Module name:", "Load module",
|
||||
wxFileName(path).GetName(), this);
|
||||
if (modname.empty()) return;
|
||||
|
||||
static const wxString kinds[] = {"mentor", "altium", "ods"};
|
||||
int ki = wxGetSingleChoiceIndex("Import type:", "Load module",
|
||||
WXSIZEOF(kinds), kinds, this);
|
||||
if (ki < 0) return;
|
||||
|
||||
ImportType type;
|
||||
app::import_type_from_name(kinds[ki].ToStdString(), type); // choice is valid
|
||||
app::LoadResult r = app::load_module(
|
||||
fe_.system(), modname.utf8_string(), path.utf8_string(), type);
|
||||
if (!r.ok) {
|
||||
Log("load failed: " + wx(r.error));
|
||||
wxMessageBox(wx(r.error), "Load failed", wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
Log(wxString::Format(
|
||||
"loaded '%s' from %s — %d part(s), %d signal(s)"
|
||||
" (dropped %d; types: %d power / %d gnd / %d suspect)",
|
||||
modname, path, r.parts, r.signals, r.dropped, r.power, r.gnd,
|
||||
r.kept_other));
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::OnRestore(wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Restore a system snapshot", "", "",
|
||||
"essim snapshots (*.essim)|*.essim|All files (*.*)|*.*",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
std::string err;
|
||||
System *fresh = restore_system(dlg.GetPath().utf8_string(), err);
|
||||
if (!fresh) {
|
||||
Log("restore failed: " + wx(err));
|
||||
wxMessageBox(wx(err), "Restore failed", wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
fe_.set_system(fresh);
|
||||
Log("restored from " + dlg.GetPath());
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::OnSave(wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Save system snapshot", "", "system.essim",
|
||||
"essim snapshots (*.essim)|*.essim|All files (*.*)|*.*",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
std::string err;
|
||||
if (save_system(fe_.system(), dlg.GetPath().utf8_string(), err)) {
|
||||
Log("saved to " + dlg.GetPath());
|
||||
} else {
|
||||
Log("save failed: " + wx(err));
|
||||
wxMessageBox(wx(err), "Save failed", wxOK | wxICON_ERROR, this);
|
||||
}
|
||||
}
|
||||
|
||||
void EssimFrame::OnExport(wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Export connections", "", "connections.csv",
|
||||
"CSV (*.csv)|*.csv|OpenDocument sheet (*.ods)|*.ods",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
const std::string path = dlg.GetPath().utf8_string();
|
||||
|
||||
app::ExportFormat fmt;
|
||||
if (!app::export_format_from_path(path, fmt)) {
|
||||
wxMessageBox("Unknown export extension (use .csv or .ods).",
|
||||
"Export failed", wxOK | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
app::ExportResult r = app::export_connections(fe_.system(), path, fmt);
|
||||
if (r.ok) {
|
||||
Log(wxString::Format("exported %d row(s) to %s", r.rows, dlg.GetPath()));
|
||||
} else {
|
||||
Log("export failed: " + wx(r.error));
|
||||
wxMessageBox(wx(r.error), "Export failed", wxOK | wxICON_ERROR, this);
|
||||
}
|
||||
}
|
||||
|
||||
void EssimFrame::OnVerify(wxCommandEvent &) {
|
||||
app::VerifyReport r = app::verify(fe_.system());
|
||||
|
||||
Log("verify:");
|
||||
Log(wxString::Format(" %d pin-role mismatch(es) over %d typed pin(s)",
|
||||
(int)r.role_mismatches.size(), r.typed_pins));
|
||||
for (const auto &m : r.role_mismatches)
|
||||
Log(wx(" " + m.module + "/" + m.part + "/" + m.pin + ": expected "
|
||||
+ signal_type_name(m.expected) + ", got "
|
||||
+ signal_type_name(m.actual)));
|
||||
|
||||
Log(wxString::Format(" %d inconsistent net(s) over %d bridged net(s)",
|
||||
(int)r.net_inconsistencies.size(), r.bridged_nets));
|
||||
Log(wxString::Format(" %d orphan pin(s) (%d imported, %d dropped)",
|
||||
r.orphan_total(), r.orphan_imported, r.orphan_dropped));
|
||||
|
||||
auto log_anoms = [this](const std::vector<Anomaly> &v, const char *tail) {
|
||||
Log(wxString::Format(" %d %s", (int)v.size(), tail));
|
||||
for (const auto &a : v)
|
||||
Log(wx(std::string(" [") + anomaly_kind_name(a.kind) + "] "
|
||||
+ a.message));
|
||||
};
|
||||
log_anoms(r.pin_anomalies, "model-driven pin anomaly(ies)");
|
||||
log_anoms(r.jtag_anomalies, "JTAG chain anomaly(ies)");
|
||||
log_anoms(r.conflict_anomalies, "source-conflict(s)");
|
||||
log_anoms(r.completeness_anomalies, "BSDL completeness issue(s)");
|
||||
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
void EssimFrame::OnQuit(wxCommandEvent &) { Close(true); }
|
||||
|
||||
void EssimFrame::OnAbout(wxCommandEvent &) {
|
||||
wxMessageBox("essim — system digital twin\n\n"
|
||||
"wxWidgets frontend over essim_core.",
|
||||
"About essim", wxOK | wxICON_INFORMATION, this);
|
||||
}
|
||||
38
src/frontends/wx/wx_frame.hpp
Normal file
38
src/frontends/wx/wx_frame.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef _WX_FRAME_HPP_
|
||||
#define _WX_FRAME_HPP_
|
||||
|
||||
#include <wx/frame.h>
|
||||
|
||||
class WxFrontend;
|
||||
class wxTreeCtrl;
|
||||
class wxTextCtrl;
|
||||
class wxCommandEvent;
|
||||
|
||||
// The essim main window. Holds no domain state of its own: it reads and mutates
|
||||
// the System owned by the WxFrontend, calling the core/app operations directly
|
||||
// (load, verify, export, save, restore) and rendering their results into a
|
||||
// model tree, an overview panel and a log.
|
||||
class EssimFrame : public wxFrame {
|
||||
public:
|
||||
explicit EssimFrame(WxFrontend &fe);
|
||||
|
||||
private:
|
||||
// Menu handlers — each is a thin wrapper over a core/app operation.
|
||||
void OnLoad(wxCommandEvent &);
|
||||
void OnRestore(wxCommandEvent &);
|
||||
void OnSave(wxCommandEvent &);
|
||||
void OnExport(wxCommandEvent &);
|
||||
void OnVerify(wxCommandEvent &);
|
||||
void OnQuit(wxCommandEvent &);
|
||||
void OnAbout(wxCommandEvent &);
|
||||
|
||||
void RebuildModelView(); ///< refresh tree + overview from the System
|
||||
void Log(const wxString &line); ///< append a line to the log pane
|
||||
|
||||
WxFrontend &fe_;
|
||||
wxTreeCtrl *tree_ = nullptr;
|
||||
wxTextCtrl *overview_ = nullptr;
|
||||
wxTextCtrl *log_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // _WX_FRAME_HPP_
|
||||
105
src/frontends/wx/wx_frontend.cpp
Normal file
105
src/frontends/wx/wx_frontend.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "frontends/wx/wx_frontend.hpp"
|
||||
|
||||
#include "frontends/wx/wx_frame.hpp"
|
||||
|
||||
#include "core/domain/connect.hpp"
|
||||
#include "core/domain/modules.hpp"
|
||||
#include "core/domain/persist.hpp"
|
||||
#include "core/domain/system.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/init.h>
|
||||
|
||||
#include <clocale>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
// Minimal wxApp: on init it shows the main window bound to the frontend.
|
||||
class EssimApp : public wxApp {
|
||||
public:
|
||||
explicit EssimApp(WxFrontend &fe) : fe_(fe) {}
|
||||
bool OnInit() override {
|
||||
// Decode the UTF-8 in our narrow string literals (em dash, ellipsis…)
|
||||
// correctly: wxString converts const char* via the C locale, which is
|
||||
// "C" (ASCII) at startup. Set only LC_CTYPE — leave LC_NUMERIC as "C"
|
||||
// so number formatting stays dot-decimal.
|
||||
std::setlocale(LC_CTYPE, "");
|
||||
(new EssimFrame(fe_))->Show(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
WxFrontend &fe_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
WxFrontend::WxFrontend() = default;
|
||||
WxFrontend::~WxFrontend() = default;
|
||||
|
||||
void WxFrontend::ensure_system() {
|
||||
if (!sys_) sys_.reset(new System());
|
||||
}
|
||||
|
||||
void WxFrontend::set_system(System *fresh) {
|
||||
sys_.reset(fresh);
|
||||
}
|
||||
|
||||
void WxFrontend::BootDispatch(const std::string &raw) {
|
||||
// The GUI has no command shell. Honour the boot commands that make sense
|
||||
// headlessly: `restore <file>` seeds a snapshot; anything else is noted.
|
||||
std::istringstream iss(raw);
|
||||
std::string cmd;
|
||||
iss >> cmd;
|
||||
std::string arg;
|
||||
std::getline(iss, arg);
|
||||
if (std::size_t b = arg.find_first_not_of(" \t"); b != std::string::npos)
|
||||
arg = arg.substr(b);
|
||||
else
|
||||
arg.clear();
|
||||
|
||||
if (cmd == "restore") {
|
||||
std::string err;
|
||||
System *fresh = restore_system(arg, err);
|
||||
if (!fresh) {
|
||||
output_ += "restore failed: " + err + "\n";
|
||||
return;
|
||||
}
|
||||
sys_.reset(fresh);
|
||||
output_ += "restored from " + arg + " ("
|
||||
+ std::to_string(sys_->modules()->size()) + " module(s), "
|
||||
+ std::to_string(sys_->connections()->size())
|
||||
+ " connection(s))\n";
|
||||
} else if (cmd == "source") {
|
||||
output_ += "source: the wx frontend has no script interpreter "
|
||||
"(use the tui frontend for scripts).\n";
|
||||
} else if (!cmd.empty()) {
|
||||
output_ += "boot: ignored '" + raw + "'.\n";
|
||||
}
|
||||
}
|
||||
|
||||
void WxFrontend::DumpCommandsMd(std::ostream &out) const {
|
||||
out << "# essim — wx frontend\n\n"
|
||||
<< "The wx frontend is menu-driven and exposes no textual command "
|
||||
<< "registry. Generate the command reference from the tui frontend "
|
||||
<< "(`-DESSIM_FRONTEND=tui`, then `essim --commands-md`).\n";
|
||||
}
|
||||
|
||||
void WxFrontend::DumpOutput(std::ostream &out) const {
|
||||
out << output_;
|
||||
}
|
||||
|
||||
void WxFrontend::Run() {
|
||||
ensure_system();
|
||||
|
||||
wxApp::SetInstance(new EssimApp(*this));
|
||||
int argc = 0;
|
||||
wxEntryStart(argc, static_cast<char **>(nullptr));
|
||||
if (wxTheApp->CallOnInit())
|
||||
wxTheApp->OnRun();
|
||||
wxTheApp->OnExit();
|
||||
wxEntryCleanup();
|
||||
}
|
||||
37
src/frontends/wx/wx_frontend.hpp
Normal file
37
src/frontends/wx/wx_frontend.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef _WX_FRONTEND_HPP_
|
||||
#define _WX_FRONTEND_HPP_
|
||||
|
||||
#include "frontends/frontend.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class System;
|
||||
|
||||
// wxWidgets GUI frontend. Implements the shared Frontend interface so the same
|
||||
// launcher (frontend_main) drives it: it owns the System and a console buffer,
|
||||
// handles boot commands headlessly (for --restore/--batch), and Run() opens the
|
||||
// wxWidgets window. The window itself (EssimFrame) drives essim_core / app::*
|
||||
// operations directly — no command shell, no TUI reuse.
|
||||
class WxFrontend : public Frontend {
|
||||
public:
|
||||
WxFrontend();
|
||||
~WxFrontend() override;
|
||||
|
||||
// --- Frontend interface ---
|
||||
void BootDispatch(const std::string &raw) override;
|
||||
void DumpCommandsMd(std::ostream &out) const override;
|
||||
void DumpOutput(std::ostream &out) const override;
|
||||
void Run() override;
|
||||
|
||||
// --- used by the window (EssimFrame) ---
|
||||
System *system() const { return sys_.get(); }
|
||||
void set_system(System *fresh); ///< take ownership (used by Restore)
|
||||
void ensure_system(); ///< create an empty System if none yet
|
||||
|
||||
private:
|
||||
std::unique_ptr<System> sys_;
|
||||
std::string output_; ///< console buffer surfaced by DumpOutput (batch)
|
||||
};
|
||||
|
||||
#endif // _WX_FRONTEND_HPP_
|
||||
Reference in New Issue
Block a user