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:
@@ -3,6 +3,7 @@
|
|||||||
#include "core/domain/bsdl_model.hpp"
|
#include "core/domain/bsdl_model.hpp"
|
||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/pin_model.hpp"
|
#include "core/domain/pin_model.hpp"
|
||||||
|
#include "core/domain/signals.hpp"
|
||||||
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
@@ -54,4 +55,24 @@ AttachBsdlResult attach_bsdl(Part *part, const std::string &path)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetSignalTypeResult set_signal_type(Signal *sig, const std::string &type_name)
|
||||||
|
{
|
||||||
|
SetSignalTypeResult r;
|
||||||
|
if (!sig) {
|
||||||
|
r.error = "no signal";
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalType t;
|
||||||
|
if (!signal_type_from_name(type_name, t)) {
|
||||||
|
r.error = "type must be one of: power, gnd, other (got: " + type_name + ")";
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig->type = t;
|
||||||
|
r.type = t;
|
||||||
|
r.ok = true;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#ifndef _APP_EDIT_HPP_
|
#ifndef _APP_EDIT_HPP_
|
||||||
#define _APP_EDIT_HPP_
|
#define _APP_EDIT_HPP_
|
||||||
|
|
||||||
|
#include "core/domain/signal_type.hpp" // SignalType
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class Part;
|
class Part;
|
||||||
|
class Signal;
|
||||||
|
|
||||||
// Application layer: UI-independent part-editing operations any frontend can
|
// Application layer: UI-independent part-editing operations any frontend can
|
||||||
// call. No console, no dialogs, no FTXUI — Part in, result struct out.
|
// call. No console, no dialogs, no FTXUI — Part in, result struct out.
|
||||||
@@ -38,6 +41,17 @@ struct AttachBsdlResult {
|
|||||||
// when the file cannot be parsed.
|
// when the file cannot be parsed.
|
||||||
AttachBsdlResult attach_bsdl(Part *part, const std::string &path);
|
AttachBsdlResult attach_bsdl(Part *part, const std::string &path);
|
||||||
|
|
||||||
|
// Outcome of overriding a signal's type from a user-supplied name.
|
||||||
|
struct SetSignalTypeResult {
|
||||||
|
bool ok = false;
|
||||||
|
std::string error; ///< set when the name isn't power/gnd/other
|
||||||
|
SignalType type = SignalType::Other; ///< the resolved type (for rendering)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set `sig`'s type from `type_name` (power | gnd | other, case-insensitive).
|
||||||
|
// Fails (ok=false, error set, no mutation) on an unrecognised name.
|
||||||
|
SetSignalTypeResult set_signal_type(Signal *sig, const std::string &type_name);
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
||||||
#endif // _APP_EDIT_HPP_
|
#endif // _APP_EDIT_HPP_
|
||||||
|
|||||||
@@ -334,14 +334,10 @@ void Tui::RegisterCommands() {
|
|||||||
catch (const std::exception &) {
|
catch (const std::exception &) {
|
||||||
Print("unknown signal: " + mod->name + "/" + args[1]); return;
|
Print("unknown signal: " + mod->name + "/" + args[1]); return;
|
||||||
}
|
}
|
||||||
SignalType t;
|
app::SetSignalTypeResult r = app::set_signal_type(sig, args[2]);
|
||||||
if (!signal_type_from_name(args[2], t)) {
|
if (!r.ok) { Print(r.error); return; }
|
||||||
Print("type must be one of: power, gnd, other (got: " + args[2] + ")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sig->type = t;
|
|
||||||
Print(mod->name + "/" + sig->name + ": signal type = "
|
Print(mod->name + "/" + sig->name + ": signal type = "
|
||||||
+ signal_type_name(t));
|
+ signal_type_name(r.type));
|
||||||
},
|
},
|
||||||
/*prompt_for_missing=*/ true,
|
/*prompt_for_missing=*/ true,
|
||||||
"override the auto-detected signal type (power | gnd | other)",
|
"override the auto-detected signal type (power | gnd | other)",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ enum {
|
|||||||
ID_EXPORT,
|
ID_EXPORT,
|
||||||
ID_SET_CONNECTOR_TYPE,
|
ID_SET_CONNECTOR_TYPE,
|
||||||
ID_ATTACH_BSDL,
|
ID_ATTACH_BSDL,
|
||||||
|
ID_SET_SIGNAL_TYPE,
|
||||||
ID_CONNECT,
|
ID_CONNECT,
|
||||||
ID_VERIFY,
|
ID_VERIFY,
|
||||||
ID_QUIT,
|
ID_QUIT,
|
||||||
@@ -61,6 +62,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
|||||||
auto *edit = new wxMenu;
|
auto *edit = new wxMenu;
|
||||||
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
||||||
edit->Append(ID_ATTACH_BSDL, "Attach &BSDL…\tCtrl-B");
|
edit->Append(ID_ATTACH_BSDL, "Attach &BSDL…\tCtrl-B");
|
||||||
|
edit->Append(ID_SET_SIGNAL_TYPE, "Set &signal type…\tCtrl-G");
|
||||||
edit->AppendSeparator();
|
edit->AppendSeparator();
|
||||||
edit->Append(ID_CONNECT, "C&onnect parts…\tCtrl-O");
|
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::OnExport, this, ID_EXPORT);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
|
Bind(wxEVT_MENU, &EssimFrame::OnSetConnectorType, this, ID_SET_CONNECTOR_TYPE);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnAttachBsdl, this, ID_ATTACH_BSDL);
|
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::OnConnect, this, ID_CONNECT);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnVerify, this, ID_VERIFY);
|
Bind(wxEVT_MENU, &EssimFrame::OnVerify, this, ID_VERIFY);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT);
|
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();
|
System *sys = fe_.system();
|
||||||
if (!sys || sys->modules()->size() == 0) {
|
if (!sys || sys->modules()->size() == 0) {
|
||||||
wxMessageBox("No modules loaded.", caption,
|
wxMessageBox("No modules loaded.", caption,
|
||||||
wxOK | wxICON_INFORMATION, this);
|
wxOK | wxICON_INFORMATION, this);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> mods;
|
std::vector<std::string> mods;
|
||||||
for (auto &mkv : *sys->modules()) mods.push_back(mkv.first);
|
for (auto &mkv : *sys->modules()) mods.push_back(mkv.first);
|
||||||
std::sort(mods.begin(), mods.end());
|
std::sort(mods.begin(), mods.end());
|
||||||
wxArrayString mchoices;
|
wxArrayString choices;
|
||||||
for (const auto &m : mods) mchoices.Add(wx(m));
|
for (const auto &m : mods) choices.Add(wx(m));
|
||||||
int mi = wxGetSingleChoiceIndex("Module:", caption, mchoices, this);
|
int mi = wxGetSingleChoiceIndex("Module:", caption, choices, this);
|
||||||
if (mi < 0) return nullptr;
|
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) {
|
if (m->size() == 0) {
|
||||||
wxMessageBox("That module has no parts.", caption,
|
wxMessageBox("That module has no parts.", caption,
|
||||||
wxOK | wxICON_INFORMATION, this);
|
wxOK | wxICON_INFORMATION, this);
|
||||||
@@ -286,13 +292,14 @@ Part *EssimFrame::PickPart(const wxString &caption) {
|
|||||||
std::vector<std::string> parts;
|
std::vector<std::string> parts;
|
||||||
for (auto &pkv : *m) parts.push_back(pkv.first);
|
for (auto &pkv : *m) parts.push_back(pkv.first);
|
||||||
std::sort(parts.begin(), parts.end());
|
std::sort(parts.begin(), parts.end());
|
||||||
wxArrayString pchoices;
|
wxArrayString choices;
|
||||||
for (const auto &p : parts) pchoices.Add(wx(p));
|
for (const auto &p : parts) choices.Add(wx(p));
|
||||||
int pi = wxGetSingleChoiceIndex("Part:", caption, pchoices, this);
|
int pi = wxGetSingleChoiceIndex("Part:", caption, choices, this);
|
||||||
if (pi < 0) return nullptr;
|
if (pi < 0) return nullptr;
|
||||||
return m->get(parts[pi]);
|
return m->get(parts[pi]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
|
void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
|
||||||
Part *p = PickPart();
|
Part *p = PickPart();
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
@@ -378,6 +385,39 @@ void EssimFrame::OnConnect(wxCommandEvent &) {
|
|||||||
RebuildModelView();
|
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 &) {
|
void EssimFrame::OnVerify(wxCommandEvent &) {
|
||||||
app::VerifyReport r = app::verify(fe_.system());
|
app::VerifyReport r = app::verify(fe_.system());
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,16 @@ private:
|
|||||||
void OnSetConnectorType(wxCommandEvent &);
|
void OnSetConnectorType(wxCommandEvent &);
|
||||||
void OnAttachBsdl(wxCommandEvent &);
|
void OnAttachBsdl(wxCommandEvent &);
|
||||||
void OnConnect(wxCommandEvent &);
|
void OnConnect(wxCommandEvent &);
|
||||||
|
void OnSetSignalType(wxCommandEvent &);
|
||||||
void OnVerify(wxCommandEvent &);
|
void OnVerify(wxCommandEvent &);
|
||||||
void OnQuit(wxCommandEvent &);
|
void OnQuit(wxCommandEvent &);
|
||||||
void OnAbout(wxCommandEvent &);
|
void OnAbout(wxCommandEvent &);
|
||||||
|
|
||||||
// Prompt the user to pick a module then a part from the current System.
|
// Modal pickers over the current System. `caption` titles the dialogs (e.g.
|
||||||
// `caption` titles the dialogs (e.g. to distinguish two picks). Returns
|
// to distinguish two picks). Each returns nullptr if there is nothing to
|
||||||
// nullptr if there is nothing to pick or the user cancels.
|
// pick or the user cancels.
|
||||||
class Part *PickPart(const wxString &caption = "Select part");
|
class Module *PickModule(const wxString &caption);
|
||||||
|
class Part *PickPart(const wxString &caption = "Select part");
|
||||||
|
|
||||||
void RebuildModelView(); ///< refresh tree + overview from the System
|
void RebuildModelView(); ///< refresh tree + overview from the System
|
||||||
void Log(const wxString &line); ///< append a line to the log pane
|
void Log(const wxString &line); ///< append a line to the log pane
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "core/app/edit.hpp"
|
#include "core/app/edit.hpp"
|
||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/pins.hpp"
|
#include "core/domain/pins.hpp"
|
||||||
|
#include "core/domain/signal_type.hpp"
|
||||||
|
#include "core/domain/signals.hpp"
|
||||||
|
|
||||||
// app::set_connector_type is pure core: validate the kind, tag the part and
|
// app::set_connector_type is pure core: validate the kind, tag the part and
|
||||||
// apply the connector model. No Print/dialog/FTXUI.
|
// apply the connector model. No Print/dialog/FTXUI.
|
||||||
@@ -54,3 +56,20 @@ TEST_CASE("attach_bsdl on a null part fails cleanly") {
|
|||||||
CHECK_FALSE(r.ok);
|
CHECK_FALSE(r.ok);
|
||||||
CHECK_FALSE(r.error.empty());
|
CHECK_FALSE(r.error.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set_signal_type parses the name and sets the type") {
|
||||||
|
Signal s("NET");
|
||||||
|
app::SetSignalTypeResult r = app::set_signal_type(&s, "power");
|
||||||
|
CHECK(r.ok);
|
||||||
|
CHECK(r.type == SignalType::Power);
|
||||||
|
CHECK(s.type == SignalType::Power);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set_signal_type rejects an unknown name without mutating") {
|
||||||
|
Signal s("NET");
|
||||||
|
s.type = SignalType::Other;
|
||||||
|
app::SetSignalTypeResult r = app::set_signal_type(&s, "bogus");
|
||||||
|
CHECK_FALSE(r.ok);
|
||||||
|
CHECK(r.error.find("power, gnd, other") != std::string::npos);
|
||||||
|
CHECK(s.type == SignalType::Other); // unchanged
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user