Extract set-signal-type into core; add it to the wx GUI.

Fourth editing op into the wx frontend. Extract the type-name parse + apply
into core/app/edit.hpp::set_signal_type(Signal*, name) -> {ok, error, type},
failing without mutation on an unrecognised name. The interactive sigtype modal
keeps its own SignalType-cycling path (different interaction, trivial mutation).

The TUI `set-signal-type` command now renders that result (output unchanged).
The wx GUI gains Edit ▸ Set signal type…: a shared PickModule() helper (PickPart
now builds on it) + inline signal choice + a power/gnd/other dropdown, then the
core op, logged as "module/signal: signal type = …" and reflected.

tests/test_edit.cpp: name parsed and applied; unknown name refused without
mutation. 387 core assertions green; tui + wx build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:27:44 +02:00
parent fc3ef333fa
commit 19dbec9672
6 changed files with 112 additions and 20 deletions

View File

@@ -35,6 +35,7 @@ enum {
ID_EXPORT,
ID_SET_CONNECTOR_TYPE,
ID_ATTACH_BSDL,
ID_SET_SIGNAL_TYPE,
ID_CONNECT,
ID_VERIFY,
ID_QUIT,
@@ -61,6 +62,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
auto *edit = new wxMenu;
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
edit->Append(ID_ATTACH_BSDL, "Attach &BSDL…\tCtrl-B");
edit->Append(ID_SET_SIGNAL_TYPE, "Set &signal type…\tCtrl-G");
edit->AppendSeparator();
edit->Append(ID_CONNECT, "C&onnect parts…\tCtrl-O");
@@ -107,6 +109,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
Bind(wxEVT_MENU, &EssimFrame::OnExport, this, ID_EXPORT);
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
Bind(wxEVT_MENU, &EssimFrame::OnAttachBsdl, this, ID_ATTACH_BSDL);
Bind(wxEVT_MENU, &EssimFrame::OnSetSignalType, this, ID_SET_SIGNAL_TYPE);
Bind(wxEVT_MENU, &EssimFrame::OnConnect, this, ID_CONNECT);
Bind(wxEVT_MENU, &EssimFrame::OnVerify, this, ID_VERIFY);
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
@@ -261,23 +264,26 @@ void EssimFrame::OnExport(wxCommandEvent &) {
}
}
Part *EssimFrame::PickPart(const wxString &caption) {
Module *EssimFrame::PickModule(const wxString &caption) {
System *sys = fe_.system();
if (!sys || sys->modules()->size() == 0) {
wxMessageBox("No modules loaded.", caption,
wxOK | wxICON_INFORMATION, this);
return nullptr;
}
std::vector<std::string> mods;
for (auto &mkv : *sys->modules()) mods.push_back(mkv.first);
std::sort(mods.begin(), mods.end());
wxArrayString mchoices;
for (const auto &m : mods) mchoices.Add(wx(m));
int mi = wxGetSingleChoiceIndex("Module:", caption, mchoices, this);
wxArrayString choices;
for (const auto &m : mods) choices.Add(wx(m));
int mi = wxGetSingleChoiceIndex("Module:", caption, choices, this);
if (mi < 0) return nullptr;
Module *m = sys->modules()->get(mods[mi]);
return sys->modules()->get(mods[mi]);
}
Part *EssimFrame::PickPart(const wxString &caption) {
Module *m = PickModule(caption);
if (!m) return nullptr;
if (m->size() == 0) {
wxMessageBox("That module has no parts.", caption,
wxOK | wxICON_INFORMATION, this);
@@ -286,13 +292,14 @@ Part *EssimFrame::PickPart(const wxString &caption) {
std::vector<std::string> parts;
for (auto &pkv : *m) parts.push_back(pkv.first);
std::sort(parts.begin(), parts.end());
wxArrayString pchoices;
for (const auto &p : parts) pchoices.Add(wx(p));
int pi = wxGetSingleChoiceIndex("Part:", caption, pchoices, this);
wxArrayString choices;
for (const auto &p : parts) choices.Add(wx(p));
int pi = wxGetSingleChoiceIndex("Part:", caption, choices, this);
if (pi < 0) return nullptr;
return m->get(parts[pi]);
}
void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
Part *p = PickPart();
if (!p) return;
@@ -378,6 +385,39 @@ void EssimFrame::OnConnect(wxCommandEvent &) {
RebuildModelView();
}
void EssimFrame::OnSetSignalType(wxCommandEvent &) {
Module *m = PickModule("Set signal type");
if (!m) return;
if (m->signals->size() == 0) {
wxMessageBox("That module has no signals.", "Set signal type",
wxOK | wxICON_INFORMATION, this);
return;
}
std::vector<std::string> sigs;
for (auto &skv : *m->signals) sigs.push_back(skv.first);
std::sort(sigs.begin(), sigs.end());
wxArrayString schoices;
for (const auto &s : sigs) schoices.Add(wx(s));
int si = wxGetSingleChoiceIndex("Signal:", "Set signal type", schoices, this);
if (si < 0) return;
Signal *sig = m->signals->get(sigs[si]);
static const wxString types[] = {"power", "gnd", "other"};
int ti = wxGetSingleChoiceIndex("Type:", "Set signal type",
WXSIZEOF(types), types, this);
if (ti < 0) return;
app::SetSignalTypeResult r = app::set_signal_type(sig, types[ti].ToStdString());
if (!r.ok) {
Log(wx(r.error));
wxMessageBox(wx(r.error), "Set signal type", wxOK | wxICON_ERROR, this);
return;
}
Log(wxString::Format("%s/%s: signal type = %s", wx(m->name), wx(sig->name),
wx(signal_type_name(r.type))));
RebuildModelView();
}
void EssimFrame::OnVerify(wxCommandEvent &) {
app::VerifyReport r = app::verify(fe_.system());