wx: enrich the explore tree down to pins and signals

The model tree was modules → parts only. Drill it down:
  - each Part now lists its Pins, each labelled with the signal it is wired to
    and that signal's type, or "(NC[, imported|dropped])";
  - each Module gains a "Signals (N)" branch — the per-module net view, each
    signal labelled with its type and fan-out.
Pin/part/signal lists sort in natural order ("2" < "10") via a small helper.
Modules expand to show their parts + Signals node; pins and the signal list
stay collapsed (revealed on demand) so large parts don't flood the view.

wx-only change (no core, no tui); builds clean, window opens with no asserts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:32:21 +02:00
parent 19dbec9672
commit d4eac9557b

View File

@@ -12,6 +12,7 @@
#include "core/domain/modules.hpp" #include "core/domain/modules.hpp"
#include "core/domain/parts.hpp" #include "core/domain/parts.hpp"
#include "core/domain/persist.hpp" #include "core/domain/persist.hpp"
#include "core/domain/pins.hpp"
#include "core/domain/signal_type.hpp" #include "core/domain/signal_type.hpp"
#include "core/domain/signals.hpp" #include "core/domain/signals.hpp"
#include "core/domain/system.hpp" #include "core/domain/system.hpp"
@@ -24,6 +25,7 @@
#include <wx/treectrl.h> #include <wx/treectrl.h>
#include <algorithm> #include <algorithm>
#include <cctype>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -44,6 +46,34 @@ enum {
// Core (UTF-8 std::string) -> wxString, and back for paths. // Core (UTF-8 std::string) -> wxString, and back for paths.
inline wxString wx(const std::string &s) { return wxString::FromUTF8(s.c_str()); } inline wxString wx(const std::string &s) { return wxString::FromUTF8(s.c_str()); }
// Natural order ("2" < "10", "A2" < "A10") so pin/part lists read intuitively.
bool natural_less(const std::string &a, const std::string &b) {
size_t i = 0, j = 0;
while (i < a.size() && j < b.size()) {
unsigned char ca = a[i], cb = b[j];
if (std::isdigit(ca) && std::isdigit(cb)) {
size_t i0 = i, j0 = j;
while (i < a.size() && std::isdigit((unsigned char)a[i])) ++i;
while (j < b.size() && std::isdigit((unsigned char)b[j])) ++j;
std::string na = a.substr(i0, i - i0), nb = b.substr(j0, j - j0);
na.erase(0, na.find_first_not_of('0')); // ignore leading zeros
nb.erase(0, nb.find_first_not_of('0'));
if (na.size() != nb.size()) return na.size() < nb.size();
if (na != nb) return na < nb;
} else {
if (ca != cb) return ca < cb;
++i; ++j;
}
}
return a.size() < b.size();
}
// " (Power)" / " (Gnd)" — only for the meaningful types; "" for Other.
wxString type_suffix(SignalType t) {
return t == SignalType::Other ? wxString()
: " (" + wxString(signal_type_name(t)) + ")";
}
} // namespace } // namespace
EssimFrame::EssimFrame(WxFrontend &fe) EssimFrame::EssimFrame(WxFrontend &fe)
@@ -143,17 +173,54 @@ void EssimFrame::RebuildModelView() {
wxTreeItemId mid = tree_->AppendItem( wxTreeItemId mid = tree_->AppendItem(
root, wx(mname) + wxString::Format(" — %d part(s), %d signal(s)", root, wx(mname) + wxString::Format(" — %d part(s), %d signal(s)",
mp, ms)); mp, ms));
// Parts → pins (each pin shows the signal it is wired to, or NC).
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(), natural_less);
for (const auto &pname : parts) { for (const auto &pname : parts) {
Part *p = m->get(pname); Part *p = m->get(pname);
wxString label = wx(pname) wxString label = wx(pname)
+ wxString::Format(" (%d pin(s))", (int)p->size()); + wxString::Format(" (%d pin(s))", (int)p->size());
if (!p->connector_type.empty()) if (!p->connector_type.empty())
label += " [" + wx(p->connector_type) + "]"; label += " [" + wx(p->connector_type) + "]";
tree_->AppendItem(mid, label); wxTreeItemId pid = tree_->AppendItem(mid, label);
std::vector<std::string> pins;
for (auto &nkv : *p) pins.push_back(nkv.first);
std::sort(pins.begin(), pins.end(), natural_less);
for (const auto &pinname : pins) {
Pin *pin = p->get(pinname);
wxString pl = wx(pinname) + " -> ";
if (Signal *s = pin->signal()) {
pl += wx(s->name) + type_suffix(s->type);
} else {
pl += "(NC";
if (pin->nc_origin == NcOrigin::ImportedUnconnected)
pl += ", imported";
else if (pin->nc_origin == NcOrigin::DroppedSingleton)
pl += ", dropped";
pl += ")";
}
tree_->AppendItem(pid, pl);
}
} }
// Signals branch (the per-module net view: type + fan-out).
if (ms > 0) {
wxTreeItemId sid =
tree_->AppendItem(mid, wxString::Format("Signals (%d)", ms));
std::vector<std::string> sigs;
for (auto &skv : *m->signals) sigs.push_back(skv.first);
std::sort(sigs.begin(), sigs.end(), natural_less);
for (const auto &sname : sigs) {
Signal *s = m->signals->get(sname);
tree_->AppendItem(sid, wx(sname) + type_suffix(s->type)
+ wxString::Format(" — %d pin(s)", (int)s->size()));
}
}
tree_->Expand(mid); // parts + Signals visible; pins/nets on demand
} }
} }
tree_->Expand(root); tree_->Expand(root);