session: plots carry row and width; files without row load one cell per row

This commit is contained in:
Martino Ferrari committed 2026-11-03 17:16:08 +01:00
1 parent ae1d262be4
commit 62ceb69278
5 files changed
+203 -6

No files matched your search

+117 -1
View File
@@ -18,6 +18,7 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <tuple>
namespace udpscope {
@@ -3900,6 +3901,34 @@ void split_probe_report(App& a, const char* tag) {
}
namespace {
/// The arrangement and what is in it, as one comparable string: per figure row its weight, per cell
/// its width share and its traces. Weights are read back as stored, so a load is compared with a save
/// to the precision the file keeps (`%.4f`).
std::string split_probe_shape(const RowSet& rs) {
std::string out;
char buf[48];
for (int fr = 0; fr < rs.figure_row_count(); ++fr) {
const std::vector<float>& rw = rs.row_weights();
const std::vector<float>& cw = rs.cell_weights(fr);
std::snprintf(buf, sizeof(buf), "%sr%d(h=%.4f)", fr == 0 ? "" : " ", fr,
static_cast<std::size_t>(fr) < rw.size() ? static_cast<double>(rw[static_cast<std::size_t>(fr)]) : 1.0);
out += buf;
const int first = rs.first_plot_of_row(fr);
for (int k = 0; k < rs.cells_in_row(fr); ++k) {
std::snprintf(buf, sizeof(buf), "[w=%.4f:", static_cast<std::size_t>(k) < cw.size() ? static_cast<double>(cw[static_cast<std::size_t>(k)]) : 1.0);
out += buf;
bool any = false;
for (const std::string& n : rs.row(first + k).signal_name) {
if (n.empty()) continue;
out += (any ? "," : "") + n;
any = true;
}
out += any ? "]" : "-]";
}
}
return out;
}
/// After any structural change: the new figure is the area's, and the frame tick re-lays it out.
void split_probe_restructured(App& a) {
if (a.area != nullptr) a.area->set_figure(&a.rows->figure());
@@ -4002,8 +4031,95 @@ gboolean split_probe_step(gpointer user) {
split_probe_restructured(*a);
split_probe_next(*a);
break;
default:
case 6: {
split_probe_report(*a, "remove");
// The session round trip, through the real entry points. Plot 0 alone on row 0, plots 1 and 2 side
// by side on row 1, each cell holding a different trace, row 0 at twice row 1's height and the
// split row at 3:1.
rs.set_cells_per_row({1, 2});
split_probe_clear_all(*a);
for (int p = 0; p < 3; ++p) split_probe_assign(*a, p, static_cast<std::size_t>(p));
rs.set_row_weights({2.0f, 1.0f});
rs.set_cell_weights(1, {3.0f, 1.0f});
a->dbg_split_expect = split_probe_shape(rs);
const LayoutState saved = collect_layout(*a);
const std::string path = std::string(g_get_tmp_dir()) + "/udpscope-split-probe.toml";
const bool wrote = save_layout(path, saved);
// Back to the plain arrangement, so a load that did nothing cannot pass.
rs.set_cells_per_row({1});
split_probe_clear_all(*a);
LayoutState loaded;
const bool read = load_layout(path, loaded);
// The same file with its `row`/`width` lines stripped: a session from before the split.
std::string legacy_path = path + ".legacy";
{
std::ifstream in(path);
std::ofstream out(legacy_path);
std::string line;
while (std::getline(in, line)) {
if (line.rfind("row = ", 0) == 0 || line.rfind("width = ", 0) == 0) continue;
out << line << "\n";
}
}
a->dbg_split_legacy_path = legacy_path;
std::remove(path.c_str());
if (read) apply_layout(*a, loaded);
std::fprintf(stderr, "[split-probe] roundtrip-io wrote=%d read=%d plots_in_file=%zu\n",
wrote ? 1 : 0, read ? 1 : 0, loaded.rows.size());
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 7: {
split_probe_report(*a, "roundtrip");
const std::string now = split_probe_shape(rs);
std::fprintf(stderr, "[split-probe] roundtrip rt=%d expect=%s got=%s\n",
now == a->dbg_split_expect ? 1 : 0, a->dbg_split_expect.c_str(), now.c_str());
rs.set_cells_per_row({1});
split_probe_clear_all(*a);
LayoutState loaded;
const bool read = load_layout(a->dbg_split_legacy_path, loaded);
std::remove(a->dbg_split_legacy_path.c_str());
int rows_unset = 1;
for (const LayoutState::Row& r : loaded.rows) {
if (r.row != -1) rows_unset = 0;
}
if (read) apply_layout(*a, loaded);
std::fprintf(stderr, "[split-probe] legacy-io read=%d plots_in_file=%zu row_unset=%d\n",
read ? 1 : 0, loaded.rows.size(), rows_unset);
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 8: {
split_probe_report(*a, "legacy");
// A `row` sequence that cannot be a layout loads as one cell per row rather than refusing the
// file: five cells in one row (over the cap), a gap, a row that goes back, and one that does not
// start at 0.
const LayoutState base = collect_layout(*a);
const std::vector<std::vector<int>> bad = {{0, 0, 0, 0, 0}, {0, 2, 2}, {0, 1, 0}, {1, 1, 2}};
std::string out;
int ok = 1;
for (const std::vector<int>& seq : bad) {
LayoutState st = base;
st.rows.clear();
for (const int r : seq) {
LayoutState::Row row = base.rows[0];
row.row = r;
st.rows.push_back(row);
}
apply_layout(*a, st);
const bool one_each = rs.figure_row_count() == static_cast<int>(seq.size()) &&
rs.row_count() == static_cast<int>(seq.size());
if (!one_each) ok = 0;
out += (out.empty() ? "" : " ") + std::to_string(rs.figure_row_count()) + "/" +
std::to_string(rs.row_count());
}
std::fprintf(stderr, "[split-probe] invalid-rows ok=%d frows/plots=[%s]\n", ok, out.c_str());
split_probe_restructured(*a);
break;
}
default:
break;
}
return G_SOURCE_REMOVE;