Extract set-connector-type into core; add it to the wx GUI.
First of the editing ops to reach the wx frontend. Extract the business logic
(validate the kind, tag the part, apply the connector model) into
core/app/edit.{hpp,cpp}: app::set_connector_type(Part*, kind) -> {ok, error,
materialised}, refusing without mutation when the kind is invalid for the part.
Both TUI call sites now use it: the `set-connector-type` command and the
interactive settype screen (de-dup) — output unchanged. The wx GUI gains an
Edit ▸ Set connector type… menu: a reusable PickPart() (module → part choice
dialogs) + a kind prompt, then the same core op, logged and reflected in the
model tree. Prune the now-dead pin_model/transform_vpx includes from
commands.cpp.
Unit-tested by tests/test_edit.cpp (free-form kind tags; invalid kind refused
without mutation; null part). tui + wx build clean; 376 core assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
31
src/core/app/edit.cpp
Normal file
31
src/core/app/edit.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "core/app/edit.hpp"
|
||||||
|
|
||||||
|
#include "core/domain/parts.hpp"
|
||||||
|
#include "core/domain/pin_model.hpp"
|
||||||
|
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind)
|
||||||
|
{
|
||||||
|
SetConnectorTypeResult r;
|
||||||
|
if (!part) {
|
||||||
|
r.error = "no part";
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string err = ValidatePartForKind(part, kind);
|
||||||
|
if (!err.empty()) {
|
||||||
|
r.error = err;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
part->connector_type = kind;
|
||||||
|
ConnectorModel model(kind);
|
||||||
|
ApplyReport rep = apply_model(part, model);
|
||||||
|
r.materialised = rep.materialised;
|
||||||
|
r.ok = true;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
27
src/core/app/edit.hpp
Normal file
27
src/core/app/edit.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef _APP_EDIT_HPP_
|
||||||
|
#define _APP_EDIT_HPP_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Part;
|
||||||
|
|
||||||
|
// Application layer: UI-independent part-editing operations any frontend can
|
||||||
|
// call. No console, no dialogs, no FTXUI — Part in, result struct out.
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
// Outcome of tagging a part's connector type. The op validates the kind, sets
|
||||||
|
// the type and applies the connector model (which may materialise the layout's
|
||||||
|
// missing NC pins); the caller renders the result.
|
||||||
|
struct SetConnectorTypeResult {
|
||||||
|
bool ok = false;
|
||||||
|
std::string error; ///< set when refused (kind invalid for the part)
|
||||||
|
int materialised = 0; ///< NC pins created from the connector layout
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tag `part`'s connector type and apply the matching connector model. Refuses
|
||||||
|
// (ok=false, error set, no mutation) when the kind is invalid for the part.
|
||||||
|
SetConnectorTypeResult set_connector_type(Part *part, const std::string &kind);
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
|
||||||
|
#endif // _APP_EDIT_HPP_
|
||||||
@@ -7,16 +7,15 @@
|
|||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/persist.hpp"
|
#include "core/domain/persist.hpp"
|
||||||
#include "core/domain/pin_role.hpp"
|
#include "core/domain/pin_role.hpp"
|
||||||
#include "core/domain/pin_model.hpp"
|
|
||||||
#include "core/domain/bsdl_model.hpp"
|
#include "core/domain/bsdl_model.hpp"
|
||||||
#include "core/domain/pins.hpp"
|
#include "core/domain/pins.hpp"
|
||||||
#include "core/domain/signals.hpp"
|
#include "core/domain/signals.hpp"
|
||||||
#include "core/domain/system.hpp"
|
#include "core/domain/system.hpp"
|
||||||
|
|
||||||
#include "core/app/connect.hpp"
|
#include "core/app/connect.hpp"
|
||||||
|
#include "core/app/edit.hpp"
|
||||||
#include "core/app/load.hpp"
|
#include "core/app/load.hpp"
|
||||||
#include "core/app/verify.hpp"
|
#include "core/app/verify.hpp"
|
||||||
#include "core/domain/transform_vpx.hpp" // ValidatePartForKind
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -397,18 +396,15 @@ void Tui::RegisterCommands() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::string err = ValidatePartForKind(prt, args[2]);
|
app::SetConnectorTypeResult r = app::set_connector_type(prt, args[2]);
|
||||||
if (!err.empty()) {
|
if (!r.ok) {
|
||||||
Print("set-connector-type refused: " + err);
|
Print("set-connector-type refused: " + r.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prt->connector_type = args[2];
|
|
||||||
ConnectorModel model(args[2]);
|
|
||||||
ApplyReport rep = apply_model(prt, model);
|
|
||||||
Print(mod->name + "/" + prt->name + ": connector_type = "
|
Print(mod->name + "/" + prt->name + ": connector_type = "
|
||||||
+ (args[2].empty() ? "(none)" : args[2]));
|
+ (args[2].empty() ? "(none)" : args[2]));
|
||||||
if (rep.materialised > 0)
|
if (r.materialised > 0)
|
||||||
Print("set-connector-type: added " + std::to_string(rep.materialised)
|
Print("set-connector-type: added " + std::to_string(r.materialised)
|
||||||
+ " NC pin(s) from the connector layout");
|
+ " NC pin(s) from the connector layout");
|
||||||
},
|
},
|
||||||
/*prompt_for_missing=*/ false,
|
/*prompt_for_missing=*/ false,
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
#include "frontends/tui/tui.hpp"
|
#include "frontends/tui/tui.hpp"
|
||||||
#include "frontends/tui/tui_helpers.hpp"
|
#include "frontends/tui/tui_helpers.hpp"
|
||||||
|
|
||||||
|
#include "core/app/edit.hpp"
|
||||||
#include "core/domain/modules.hpp"
|
#include "core/domain/modules.hpp"
|
||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/pin_model.hpp"
|
|
||||||
#include "core/domain/pin_role.hpp"
|
#include "core/domain/pin_role.hpp"
|
||||||
#include "core/domain/pins.hpp"
|
#include "core/domain/pins.hpp"
|
||||||
#include "core/domain/system.hpp"
|
#include "core/domain/system.hpp"
|
||||||
#include "core/domain/transform_vpx.hpp"
|
|
||||||
|
|
||||||
#include <ftxui/component/component.hpp>
|
#include <ftxui/component/component.hpp>
|
||||||
#include <ftxui/component/component_options.hpp>
|
#include <ftxui/component/component_options.hpp>
|
||||||
@@ -63,14 +62,11 @@ Component Tui::BuildSettypeScreen() {
|
|||||||
try {
|
try {
|
||||||
Module *mod = sys->modules()->get(settype_modules[settype_m_idx]);
|
Module *mod = sys->modules()->get(settype_modules[settype_m_idx]);
|
||||||
Part *prt = mod->get(settype_p_list[settype_p_idx]);
|
Part *prt = mod->get(settype_p_list[settype_p_idx]);
|
||||||
std::string err = ValidatePartForKind(prt, settype_type);
|
app::SetConnectorTypeResult r = app::set_connector_type(prt, settype_type);
|
||||||
if (!err.empty()) {
|
if (!r.ok) {
|
||||||
settype_status = "refused: " + err;
|
settype_status = "refused: " + r.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prt->connector_type = settype_type;
|
|
||||||
ConnectorModel model(settype_type);
|
|
||||||
apply_model(prt, model);
|
|
||||||
std::string msg = mod->name + "/" + prt->name + " = "
|
std::string msg = mod->name + "/" + prt->name + " = "
|
||||||
+ (settype_type.empty() ? "(none)" : settype_type);
|
+ (settype_type.empty() ? "(none)" : settype_type);
|
||||||
settype_status = "applied: " + msg;
|
settype_status = "applied: " + msg;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "frontends/wx/wx_frontend.hpp"
|
#include "frontends/wx/wx_frontend.hpp"
|
||||||
|
|
||||||
|
#include "core/app/edit.hpp"
|
||||||
#include "core/app/export.hpp"
|
#include "core/app/export.hpp"
|
||||||
#include "core/app/load.hpp"
|
#include "core/app/load.hpp"
|
||||||
#include "core/app/verify.hpp"
|
#include "core/app/verify.hpp"
|
||||||
@@ -31,6 +32,7 @@ enum {
|
|||||||
ID_RESTORE,
|
ID_RESTORE,
|
||||||
ID_SAVE,
|
ID_SAVE,
|
||||||
ID_EXPORT,
|
ID_EXPORT,
|
||||||
|
ID_SET_CONNECTOR_TYPE,
|
||||||
ID_VERIFY,
|
ID_VERIFY,
|
||||||
ID_QUIT,
|
ID_QUIT,
|
||||||
ID_ABOUT,
|
ID_ABOUT,
|
||||||
@@ -53,6 +55,9 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
|||||||
file->AppendSeparator();
|
file->AppendSeparator();
|
||||||
file->Append(ID_QUIT, "&Quit\tCtrl-Q");
|
file->Append(ID_QUIT, "&Quit\tCtrl-Q");
|
||||||
|
|
||||||
|
auto *edit = new wxMenu;
|
||||||
|
edit->Append(ID_SET_CONNECTOR_TYPE, "Set &connector type…\tCtrl-T");
|
||||||
|
|
||||||
auto *sysm = new wxMenu;
|
auto *sysm = new wxMenu;
|
||||||
sysm->Append(ID_VERIFY, "&Verify\tCtrl-K");
|
sysm->Append(ID_VERIFY, "&Verify\tCtrl-K");
|
||||||
|
|
||||||
@@ -61,6 +66,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
|||||||
|
|
||||||
auto *bar = new wxMenuBar;
|
auto *bar = new wxMenuBar;
|
||||||
bar->Append(file, "&File");
|
bar->Append(file, "&File");
|
||||||
|
bar->Append(edit, "&Edit");
|
||||||
bar->Append(sysm, "&System");
|
bar->Append(sysm, "&System");
|
||||||
bar->Append(help, "&Help");
|
bar->Append(help, "&Help");
|
||||||
SetMenuBar(bar);
|
SetMenuBar(bar);
|
||||||
@@ -93,6 +99,7 @@ EssimFrame::EssimFrame(WxFrontend &fe)
|
|||||||
Bind(wxEVT_MENU, &EssimFrame::OnRestore, this, ID_RESTORE);
|
Bind(wxEVT_MENU, &EssimFrame::OnRestore, this, ID_RESTORE);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnSave, this, ID_SAVE);
|
Bind(wxEVT_MENU, &EssimFrame::OnSave, this, ID_SAVE);
|
||||||
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::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);
|
||||||
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT);
|
||||||
@@ -246,6 +253,62 @@ void EssimFrame::OnExport(wxCommandEvent &) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Part *EssimFrame::PickPart() {
|
||||||
|
System *sys = fe_.system();
|
||||||
|
if (!sys || sys->modules()->size() == 0) {
|
||||||
|
wxMessageBox("No modules loaded.", "Select part",
|
||||||
|
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:", "Select part", mchoices, this);
|
||||||
|
if (mi < 0) return nullptr;
|
||||||
|
Module *m = sys->modules()->get(mods[mi]);
|
||||||
|
|
||||||
|
if (m->size() == 0) {
|
||||||
|
wxMessageBox("That module has no parts.", "Select part",
|
||||||
|
wxOK | wxICON_INFORMATION, this);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
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:", "Select part", pchoices, this);
|
||||||
|
if (pi < 0) return nullptr;
|
||||||
|
return m->get(parts[pi]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EssimFrame::OnSetConnectorType(wxCommandEvent &) {
|
||||||
|
Part *p = PickPart();
|
||||||
|
if (!p) return;
|
||||||
|
|
||||||
|
wxTextEntryDialog dlg(this, "Connector type (empty = none):",
|
||||||
|
"Set connector type", wx(p->connector_type));
|
||||||
|
if (dlg.ShowModal() != wxID_OK) return;
|
||||||
|
const std::string kind = dlg.GetValue().utf8_string();
|
||||||
|
|
||||||
|
app::SetConnectorTypeResult r = app::set_connector_type(p, kind);
|
||||||
|
if (!r.ok) {
|
||||||
|
Log("set-connector-type refused: " + wx(r.error));
|
||||||
|
wxMessageBox(wx(r.error), "Refused", wxOK | wxICON_ERROR, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString who = (p->prnt ? wx(p->prnt->name) + "/" : wxString()) + wx(p->name);
|
||||||
|
Log(who + ": connector_type = " + (kind.empty() ? wxString("(none)") : wx(kind)));
|
||||||
|
if (r.materialised > 0)
|
||||||
|
Log(wxString::Format(" added %d NC pin(s) from the connector layout",
|
||||||
|
r.materialised));
|
||||||
|
RebuildModelView();
|
||||||
|
}
|
||||||
|
|
||||||
void EssimFrame::OnVerify(wxCommandEvent &) {
|
void EssimFrame::OnVerify(wxCommandEvent &) {
|
||||||
app::VerifyReport r = app::verify(fe_.system());
|
app::VerifyReport r = app::verify(fe_.system());
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ private:
|
|||||||
void OnRestore(wxCommandEvent &);
|
void OnRestore(wxCommandEvent &);
|
||||||
void OnSave(wxCommandEvent &);
|
void OnSave(wxCommandEvent &);
|
||||||
void OnExport(wxCommandEvent &);
|
void OnExport(wxCommandEvent &);
|
||||||
|
void OnSetConnectorType(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.
|
||||||
|
// Returns nullptr if there is nothing to pick or the user cancels.
|
||||||
|
class Part *PickPart();
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
39
tests/test_edit.cpp
Normal file
39
tests/test_edit.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
|
#include "core/app/edit.hpp"
|
||||||
|
#include "core/domain/parts.hpp"
|
||||||
|
#include "core/domain/pins.hpp"
|
||||||
|
|
||||||
|
// app::set_connector_type is pure core: validate the kind, tag the part and
|
||||||
|
// apply the connector model. No Print/dialog/FTXUI.
|
||||||
|
|
||||||
|
TEST_CASE("set_connector_type tags a part with a free-form kind") {
|
||||||
|
Part p("J1");
|
||||||
|
p.add(new Pin("1"));
|
||||||
|
p.add(new Pin("2"));
|
||||||
|
|
||||||
|
app::SetConnectorTypeResult r = app::set_connector_type(&p, "myconn");
|
||||||
|
|
||||||
|
CHECK(r.ok);
|
||||||
|
CHECK(r.error.empty());
|
||||||
|
CHECK(p.connector_type == "myconn");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set_connector_type refuses a kind the part doesn't fit — no mutation") {
|
||||||
|
Part p("J1");
|
||||||
|
p.add(new Pin("1"));
|
||||||
|
p.add(new Pin("2"));
|
||||||
|
p.add(new Pin("3")); // numeric pins don't fit the VPX single-letter columns
|
||||||
|
|
||||||
|
app::SetConnectorTypeResult r = app::set_connector_type(&p, "vpx-3u-bkp-p0");
|
||||||
|
|
||||||
|
CHECK_FALSE(r.ok);
|
||||||
|
CHECK_FALSE(r.error.empty());
|
||||||
|
CHECK(p.connector_type.empty()); // refused before any change
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set_connector_type on a null part fails cleanly") {
|
||||||
|
app::SetConnectorTypeResult r = app::set_connector_type(nullptr, "x");
|
||||||
|
CHECK_FALSE(r.ok);
|
||||||
|
CHECK_FALSE(r.error.empty());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user