plot host: rows hold 1..4 cells; split probe scaffold
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
79e69c28fa
commit
ae1d262be4
5 files changed
+433
-18
No files matched your search
+161
-10
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
|
||||
namespace udpscope {
|
||||
namespace {
|
||||
@@ -67,6 +68,9 @@ constexpr float kMeasureDashOff = 4.0f;
|
||||
/// a box, so there was a widget gap; a figure tiles its viewport, and without a gap two
|
||||
/// stacked frames read as one tall plot with a line through it.
|
||||
constexpr plot::f32 kRowGap = 6.0f;
|
||||
/// The gap between two cells of a split row, device pixels: the same as the rows', so the
|
||||
/// plots read as a grid.
|
||||
constexpr plot::f32 kColGap = 6.0f;
|
||||
|
||||
/// The suffix that names a persistence layer, in the accumulate path's own vocabulary --
|
||||
/// ` (A)`, ` (B)`, ` #4` -- so that `layer_base_name()` takes it back off, and the signal list, the
|
||||
@@ -153,11 +157,56 @@ void RowSet::clear_signal_color(const std::string& signal) {
|
||||
|
||||
RowSet::RowSet() { rebuild_all(); }
|
||||
|
||||
int RowSet::cells_in_row(int fr) const {
|
||||
return fr >= 0 && fr < figure_row_count() ? cells_per_row_[static_cast<std::size_t>(fr)] : 0;
|
||||
}
|
||||
|
||||
int RowSet::first_plot_of_row(int fr) const {
|
||||
int p = 0;
|
||||
for (int r = 0; r < fr && r < figure_row_count(); ++r) p += cells_per_row_[static_cast<std::size_t>(r)];
|
||||
return p;
|
||||
}
|
||||
|
||||
int RowSet::figure_row_of(int plot) const {
|
||||
if (plot < 0 || plot >= rows_) return -1;
|
||||
int p = 0;
|
||||
for (int r = 0; r < figure_row_count(); ++r) {
|
||||
p += cells_per_row_[static_cast<std::size_t>(r)];
|
||||
if (plot < p) return r;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int RowSet::cell_of(int plot) const {
|
||||
const int fr = figure_row_of(plot);
|
||||
return fr < 0 ? -1 : plot - first_plot_of_row(fr);
|
||||
}
|
||||
|
||||
const std::vector<float>& RowSet::cell_weights(int fr) const {
|
||||
static const std::vector<float> none;
|
||||
return fr >= 0 && fr < static_cast<int>(cell_weights_.size())
|
||||
? cell_weights_[static_cast<std::size_t>(fr)]
|
||||
: none;
|
||||
}
|
||||
|
||||
std::string RowSet::plot_label(int plot) const {
|
||||
const int fr = figure_row_of(plot);
|
||||
if (fr < 0) return std::to_string(plot + 1);
|
||||
if (cells_per_row_[static_cast<std::size_t>(fr)] == 1) return std::to_string(fr + 1);
|
||||
return std::to_string(fr + 1) + static_cast<char>('a' + cell_of(plot));
|
||||
}
|
||||
|
||||
void RowSet::set_row_count(int rows) {
|
||||
const int want = std::clamp(rows, 1, kMaxRows);
|
||||
if (want == rows_) return;
|
||||
const bool all_single =
|
||||
std::all_of(cells_per_row_.begin(), cells_per_row_.end(), [](int c) { return c == 1; });
|
||||
if (want == rows_ && all_single) return;
|
||||
// `n` single-cell rows. The plots keep their flattened slots (and so their contents), which is
|
||||
// what the old "n rows" meant.
|
||||
rows_ = want;
|
||||
// A `Figure`'s row count is fixed at construction, so a new count is a new figure.
|
||||
cells_per_row_.assign(static_cast<std::size_t>(want), 1);
|
||||
cell_weights_.assign(static_cast<std::size_t>(want), {});
|
||||
// A `Figure`'s shape is fixed at construction, so a new count is a new figure.
|
||||
rebuild_all();
|
||||
}
|
||||
|
||||
@@ -194,23 +243,103 @@ void RowSet::clear_row(Row& r) {
|
||||
|
||||
bool RowSet::insert_row(int at) {
|
||||
if (rows_ >= kMaxRows) return false;
|
||||
const int pos = std::clamp(at, 0, rows_);
|
||||
const int fr = std::clamp(at, 0, figure_row_count());
|
||||
const int pos = first_plot_of_row(fr);
|
||||
for (int i = rows_; i > pos; --i) carry_row(row(i), row(i - 1));
|
||||
clear_row(row(pos));
|
||||
++rows_;
|
||||
cells_per_row_.insert(cells_per_row_.begin() + fr, 1);
|
||||
cell_weights_.insert(cell_weights_.begin() + fr, std::vector<float>{});
|
||||
rebuild_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RowSet::remove_row(int at) {
|
||||
if (rows_ <= 1 || at < 0 || at >= rows_) return false;
|
||||
for (int i = at; i + 1 < rows_; ++i) carry_row(row(i), row(i + 1));
|
||||
--rows_;
|
||||
clear_row(row(rows_));
|
||||
if (figure_row_count() <= 1 || at < 0 || at >= figure_row_count()) return false;
|
||||
const int pos = first_plot_of_row(at);
|
||||
const int n = cells_per_row_[static_cast<std::size_t>(at)];
|
||||
// The rows below move up by the removed row's cell count; the `n` slots freed at the end go back
|
||||
// to empty.
|
||||
for (int i = pos; i + n < rows_; ++i) carry_row(row(i), row(i + n));
|
||||
for (int i = rows_ - n; i < rows_; ++i) clear_row(row(i));
|
||||
rows_ -= n;
|
||||
cells_per_row_.erase(cells_per_row_.begin() + at);
|
||||
cell_weights_.erase(cell_weights_.begin() + at);
|
||||
rebuild_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RowSet::insert_cell(int fr, int at) {
|
||||
if (fr < 0 || fr >= figure_row_count()) return false;
|
||||
if (cells_per_row_[static_cast<std::size_t>(fr)] >= kMaxCellsPerRow || rows_ >= kMaxRows) {
|
||||
return false;
|
||||
}
|
||||
const int n = cells_per_row_[static_cast<std::size_t>(fr)];
|
||||
const int k = std::clamp(at, 0, n);
|
||||
const int p = first_plot_of_row(fr) + k;
|
||||
for (int i = rows_; i > p; --i) carry_row(row(i), row(i - 1));
|
||||
clear_row(row(p));
|
||||
++rows_;
|
||||
++cells_per_row_[static_cast<std::size_t>(fr)];
|
||||
auto& w = cell_weights_[static_cast<std::size_t>(fr)];
|
||||
if (!w.empty()) {
|
||||
// The new cell takes the mean share, so it neither dwarfs nor vanishes beside its neighbours.
|
||||
float mean = 0.0f;
|
||||
for (const float v : w) mean += v;
|
||||
mean /= static_cast<float>(w.size());
|
||||
w.insert(w.begin() + std::min<int>(k, static_cast<int>(w.size())), mean);
|
||||
}
|
||||
rebuild_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RowSet::remove_cell(int plot) {
|
||||
const int fr = figure_row_of(plot);
|
||||
if (fr < 0) return false;
|
||||
if (cells_per_row_[static_cast<std::size_t>(fr)] == 1) {
|
||||
return remove_row(fr); // false when it is the last row
|
||||
}
|
||||
for (int i = plot; i + 1 < rows_; ++i) carry_row(row(i), row(i + 1));
|
||||
--rows_;
|
||||
clear_row(row(rows_));
|
||||
const int k = plot - first_plot_of_row(fr);
|
||||
--cells_per_row_[static_cast<std::size_t>(fr)];
|
||||
auto& w = cell_weights_[static_cast<std::size_t>(fr)];
|
||||
if (cells_per_row_[static_cast<std::size_t>(fr)] < 2) {
|
||||
w.clear();
|
||||
} else if (k < static_cast<int>(w.size())) {
|
||||
w.erase(w.begin() + k);
|
||||
}
|
||||
rebuild_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RowSet::set_cell_weights(int fr, std::vector<float> w) {
|
||||
if (fr < 0 || fr >= figure_row_count()) return;
|
||||
cell_weights_[static_cast<std::size_t>(fr)] = std::move(w);
|
||||
if (figure_ != nullptr) apply_cell_weights();
|
||||
}
|
||||
|
||||
void RowSet::set_cells_per_row(std::vector<int> cells) {
|
||||
std::vector<int> next;
|
||||
int total = 0;
|
||||
for (int c : cells) {
|
||||
c = std::clamp(c, 1, kMaxCellsPerRow);
|
||||
if (total + c > kMaxRows) c = kMaxRows - total;
|
||||
if (c < 1) break;
|
||||
next.push_back(c);
|
||||
total += c;
|
||||
}
|
||||
if (next.empty()) {
|
||||
next.push_back(1);
|
||||
total = 1;
|
||||
}
|
||||
cells_per_row_ = std::move(next);
|
||||
cell_weights_.assign(cells_per_row_.size(), {});
|
||||
rows_ = total;
|
||||
rebuild_all();
|
||||
}
|
||||
|
||||
void RowSet::install(Row& r, int /*row_index*/, plot::Plot& p) {
|
||||
r.plot = &p;
|
||||
r.axis_x = r.plot->add_x_axis(
|
||||
@@ -302,14 +431,36 @@ void RowSet::set_row_weights(std::vector<float> weights) {
|
||||
|
||||
void RowSet::apply_row_weights() {
|
||||
if (row_weights_.empty() || figure_ == nullptr) return;
|
||||
row_weights_.resize(static_cast<std::size_t>(rows_), 1.0f);
|
||||
row_weights_.resize(static_cast<std::size_t>(figure_row_count()), 1.0f);
|
||||
(void)figure_->set_row_weights(row_weights_);
|
||||
}
|
||||
|
||||
void RowSet::apply_cell_weights() {
|
||||
if (figure_ == nullptr) return;
|
||||
cell_weights_.resize(cells_per_row_.size());
|
||||
for (int fr = 0; fr < figure_row_count(); ++fr) {
|
||||
auto& w = cell_weights_[static_cast<std::size_t>(fr)];
|
||||
const int n = cells_per_row_[static_cast<std::size_t>(fr)];
|
||||
if (n < 2) {
|
||||
w.clear();
|
||||
continue;
|
||||
}
|
||||
if (w.empty()) {
|
||||
figure_->clear_cell_weights(static_cast<plot::u32>(fr));
|
||||
continue;
|
||||
}
|
||||
w.resize(static_cast<std::size_t>(n), 1.0f);
|
||||
(void)figure_->set_cell_weights(static_cast<plot::u32>(fr), w);
|
||||
}
|
||||
}
|
||||
|
||||
void RowSet::rebuild_all() {
|
||||
figure_ = std::make_unique<plot::Figure>(static_cast<plot::u32>(rows_));
|
||||
const std::vector<plot::u32> cells(cells_per_row_.begin(), cells_per_row_.end());
|
||||
figure_ = std::make_unique<plot::Figure>(std::span<const plot::u32>(cells));
|
||||
figure_->set_row_gap(kRowGap);
|
||||
figure_->set_col_gap(kColGap);
|
||||
apply_row_weights();
|
||||
apply_cell_weights();
|
||||
x_group_ = figure_->add_x_link();
|
||||
for (int i = 0; i < kMaxRows; ++i) {
|
||||
Row& r = row(i);
|
||||
@@ -917,7 +1068,7 @@ bool RowSet::update_time_labels() {
|
||||
if (!relative_x_) r.plot->set_x_axis_origin(r.axis_x, 0.0);
|
||||
// The caption belongs with the labels, which a figure draws once, on the bottom row (the
|
||||
// one whose x axis is the figure's). Every other row gets an empty one, which draws nothing.
|
||||
const bool bottom = i == rows_ - 1;
|
||||
const bool bottom = i >= first_plot_of_row(figure_row_count() - 1);
|
||||
if (!relative_x_ || !bottom) {
|
||||
r.plot->set_x_note("");
|
||||
continue;
|
||||
|
||||
+51
-8
@@ -155,8 +155,43 @@ class RowSet {
|
||||
double ruler_b_y = 0.0;
|
||||
};
|
||||
|
||||
/// The number of **plots** (cells over all figure rows), which is what `row(i)` indexes. With no
|
||||
/// row split it is also the number of figure rows; `figure_row_count()` is the latter.
|
||||
int row_count() const noexcept { return rows_; }
|
||||
|
||||
/// A figure row holds one to `kMaxCellsPerRow` plots side by side, sharing the row's height and
|
||||
/// the figure's one x window. The flattened plot index (`row(i)`, `Row::plot`, the figure's
|
||||
/// `plot(i)`) runs row-major: row 0's cells left to right, then row 1's.
|
||||
static constexpr int kMaxCellsPerRow = 4;
|
||||
int figure_row_count() const noexcept { return static_cast<int>(cells_per_row_.size()); }
|
||||
/// How many cells figure row `fr` holds; 0 for a row that does not exist.
|
||||
int cells_in_row(int fr) const;
|
||||
/// The flattened index of figure row `fr`'s first cell; `row_count()` for `fr ==
|
||||
/// figure_row_count()`.
|
||||
int first_plot_of_row(int fr) const;
|
||||
/// The figure row plot `plot` is in, or -1 for an index that is not a plot.
|
||||
int figure_row_of(int plot) const;
|
||||
/// Plot `plot`'s 0-based position inside its row, or -1.
|
||||
int cell_of(int plot) const;
|
||||
/// Figure row `fr`'s cell width shares (empty means equal; always empty for a single-cell row).
|
||||
const std::vector<float>& cell_weights(int fr) const;
|
||||
/// What the UI calls a plot: the figure row's number (`"2"`) for a single-cell row, the number
|
||||
/// and a letter (`"2a"`..`"2d"`) for a split one.
|
||||
std::string plot_label(int plot) const;
|
||||
|
||||
/// Adds an empty cell to figure row `fr` at position `at` (clamped to the row), pushing the
|
||||
/// cells at and after it right. False at `kMaxCellsPerRow` cells or `kMaxRows` plots.
|
||||
bool insert_cell(int fr, int at);
|
||||
/// Removes plot `plot`. A single-cell row removes the row itself (`remove_row()`); false when
|
||||
/// that would leave no plot.
|
||||
bool remove_cell(int plot);
|
||||
/// Figure row `fr`'s cell width shares, normalised by the plotter; empty means equal.
|
||||
void set_cell_weights(int fr, std::vector<float> w);
|
||||
/// Replaces the whole arrangement: `cells[r]` plots in figure row `r`, each clamped to
|
||||
/// `1..kMaxCellsPerRow` and the total to `kMaxRows`. Weights are reset; plot contents stay in
|
||||
/// their flattened slots.
|
||||
void set_cells_per_row(std::vector<int> cells);
|
||||
|
||||
/// The alpha every trace is drawn with. The *group* views use it: accumulating events wants their
|
||||
/// traces translucent so that agreement between them reads as brightness, which is the whole point
|
||||
/// of a persistence display. It is mixed into the palette colour where the trace is created, so it
|
||||
@@ -169,22 +204,24 @@ class RowSet {
|
||||
/// trace set or the row count changes, so weights that lived only in the figure would be lost on the
|
||||
/// first drop or row change after a user had sized their layout.
|
||||
void set_row_weights(std::vector<float> weights);
|
||||
/// Sizes the stored weights to the rows that exist and hands them to the figure.
|
||||
/// Sizes the stored weights to the figure rows that exist and hands them to the figure. Weights
|
||||
/// are per **figure row**, not per plot.
|
||||
void apply_row_weights();
|
||||
const std::vector<float>& row_weights() const noexcept { return row_weights_; }
|
||||
/// Shows `rows` rows. A changed count rebuilds the figure, since a `Figure`'s row
|
||||
/// count is fixed at construction.
|
||||
/// Shows `rows` figure rows of one cell each (so `rows` plots, any split undone). A changed
|
||||
/// arrangement rebuilds the figure, since a `Figure`'s shape is fixed at construction.
|
||||
void set_row_count(int rows);
|
||||
/// Inserts an empty row at `at` (0 = above the first row, `row_count()` = below the
|
||||
/// last), pushing the rows at and below it down. Returns false at `kMaxRows`.
|
||||
/// Inserts an empty single-cell figure row at `at` (0 = above the first row,
|
||||
/// `figure_row_count()` = below the last), pushing the rows at and below it down with their
|
||||
/// cells. Returns false at `kMaxRows` plots.
|
||||
///
|
||||
/// The rows below move as *rows*: their trace assignments, colours and cursors follow
|
||||
/// them, so inserting above row 1 does not renumber what row 1 was plotting. The
|
||||
/// figure's `Plot`s are not what moves -- they are rebuilt and re-dealt -- which is
|
||||
/// the same mechanism a row-count change already used.
|
||||
bool insert_row(int at);
|
||||
/// Removes row `at` and pulls the rows below it up. Returns false when only one row
|
||||
/// is left: a figure with no rows has nothing to draw and no way back.
|
||||
/// Removes figure row `at` (with all its cells) and pulls the rows below it up. Returns false
|
||||
/// when only one row is left: a figure with no rows has nothing to draw and no way back.
|
||||
bool remove_row(int at);
|
||||
Row& row(int i) { return storage_[static_cast<std::size_t>(i)]; }
|
||||
const Row& row(int i) const { return storage_[static_cast<std::size_t>(i)]; }
|
||||
@@ -563,9 +600,15 @@ class RowSet {
|
||||
std::unique_ptr<plot::Figure> figure_;
|
||||
/// The one x link group every visible row belongs to. Groups are numbered from 1.
|
||||
plot::u32 x_group_ = 0;
|
||||
/// The number of plots; `sum(cells_per_row_) == rows_` always.
|
||||
int rows_ = 1;
|
||||
/// Cells per figure row, and each row's cell width shares (empty = equal; kept empty for a
|
||||
/// single-cell row). Both are state of the `RowSet`, restated to every new figure.
|
||||
std::vector<int> cells_per_row_{1};
|
||||
std::vector<std::vector<float>> cell_weights_ = std::vector<std::vector<float>>(1);
|
||||
void apply_cell_weights();
|
||||
float trace_alpha_ = 1.0f;
|
||||
std::vector<float> row_weights_;
|
||||
std::vector<float> row_weights_; ///< per figure row
|
||||
double window_s_ = 2.0;
|
||||
double trigger_marker_x_ = 0.0;
|
||||
bool trigger_marker_on_ = false;
|
||||
|
||||
@@ -6003,6 +6003,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_LAYOUT_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), layout_probe_step, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_SPLIT_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), split_probe_step, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_TRIG_TRACE_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), trig_trace_probe, &app);
|
||||
}
|
||||
|
||||
@@ -221,6 +221,8 @@ struct App {
|
||||
/// a frame apart, because the buttons are placed from the rects the last frame was laid
|
||||
/// out with, so the sequence cannot be one function.
|
||||
int dbg_layout_step = 0;
|
||||
/// Which step of the split probe runs next (`UDPSCOPE_PROBE_SPLIT_MS`).
|
||||
int dbg_split_step = 0;
|
||||
/// Which step of the cursors probe runs next (`UDPSCOPE_PROBE_CURSORS_MS`).
|
||||
int dbg_cursors_step = 0;
|
||||
/// Which step of the fit probe runs next (`UDPSCOPE_PROBE_FIT_MS`).
|
||||
@@ -2095,6 +2097,9 @@ void layout_probe_report(App& a, const char* what);
|
||||
void layout_click(GtkWidget* b);
|
||||
void layout_probe_next(App& a, int step, guint delay_ms);
|
||||
gboolean layout_probe_step(gpointer user);
|
||||
void split_probe_report(App& a, const char* tag);
|
||||
void split_probe_next(App& a);
|
||||
gboolean split_probe_step(gpointer user);
|
||||
gboolean enable_all_probe(gpointer user);
|
||||
gboolean disarm_probe_step2(gpointer user);
|
||||
gboolean disarm_probe_step3(gpointer user);
|
||||
|
||||
@@ -3796,6 +3796,219 @@ gboolean layout_probe_step(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// --- the horizontal plot split (`UDPSCOPE_PROBE_SPLIT_MS`) -------------------------------------
|
||||
//
|
||||
// Each step changes the arrangement of cells, re-points the area at the new figure, and lets the
|
||||
// next step (250 ms on) read what a frame was actually laid out with.
|
||||
|
||||
void split_probe_report(App& a, const char* tag) {
|
||||
RowSet& rs = *a.rows;
|
||||
const double scale = (a.area != nullptr && a.area->device_scale() > 0.0f)
|
||||
? static_cast<double>(a.area->device_scale()) : 1.0;
|
||||
const plot::FigureLayout& fl = rs.figure().layout();
|
||||
const int frows = rs.figure_row_count();
|
||||
// tile: every row's cells fill its rect edge to edge, share its y and height, and are one
|
||||
// `col_gap` apart.
|
||||
int tile = fl.cells.size() == static_cast<std::size_t>(rs.row_count()) &&
|
||||
fl.rows.size() == static_cast<std::size_t>(frows)
|
||||
? 1 : 0;
|
||||
std::string cells_s;
|
||||
std::string widths_s;
|
||||
std::string weights_s;
|
||||
for (int fr = 0; fr < frows && tile; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
const plot::Rect& row = fl.rows[static_cast<std::size_t>(fr)];
|
||||
const plot::Rect& c0 = fl.cells[static_cast<std::size_t>(first)];
|
||||
const plot::Rect& cn = fl.cells[static_cast<std::size_t>(first + n - 1)];
|
||||
if (std::fabs(c0.x - row.x) > 0.5f) tile = 0;
|
||||
if (std::fabs(cn.right() - row.right()) > 0.5f) tile = 0;
|
||||
for (int k = 0; k < n; ++k) {
|
||||
const plot::Rect& c = fl.cells[static_cast<std::size_t>(first + k)];
|
||||
if (std::fabs(c.y - row.y) > 0.5f || std::fabs(c.h - row.h) > 0.5f) tile = 0;
|
||||
if (k > 0) {
|
||||
const plot::Rect& prev = fl.cells[static_cast<std::size_t>(first + k - 1)];
|
||||
if (std::fabs((c.x - prev.right()) - fl.col_gap) > 0.5f) tile = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int fr = 0; fr < frows; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
cells_s += (fr == 0 ? "" : ",") + std::to_string(n);
|
||||
widths_s += fr == 0 ? "" : "|";
|
||||
weights_s += fr == 0 ? "" : "|";
|
||||
for (int k = 0; k < n; ++k) {
|
||||
char buf[32];
|
||||
const std::size_t idx = static_cast<std::size_t>(first + k);
|
||||
std::snprintf(buf, sizeof(buf), "%s%.0f", k == 0 ? "" : ",",
|
||||
idx < fl.cells.size() ? static_cast<double>(fl.cells[idx].w) / scale : -1.0);
|
||||
widths_s += buf;
|
||||
}
|
||||
const std::vector<float>& w = rs.cell_weights(fr);
|
||||
if (w.empty()) {
|
||||
weights_s += "-";
|
||||
} else {
|
||||
for (std::size_t k = 0; k < w.size(); ++k) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%s%g", k == 0 ? "" : ",", static_cast<double>(w[k]));
|
||||
weights_s += buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
// x_equal: every plot's x range is plot 0's. cursors_aligned: every plot's two vertical cursors
|
||||
// sit where plot 0's do.
|
||||
int x_equal = 1;
|
||||
int aligned = 1;
|
||||
const RowSet::Row& r0 = rs.row(0);
|
||||
if (r0.plot != nullptr) {
|
||||
const plot::Range ref = r0.plot->x_range(r0.axis_x);
|
||||
const double ref_a = r0.plot->cursor(r0.cursor_a).x;
|
||||
const double ref_b = r0.plot->cursor(r0.cursor_b).x;
|
||||
const double span = std::max(1e-12, std::fabs(ref.max - ref.min));
|
||||
for (int p = 1; p < rs.row_count(); ++p) {
|
||||
const RowSet::Row& r = rs.row(p);
|
||||
if (r.plot == nullptr) { x_equal = 0; aligned = 0; continue; }
|
||||
const plot::Range x = r.plot->x_range(r.axis_x);
|
||||
if (std::fabs(x.min - ref.min) > 1e-9 * span || std::fabs(x.max - ref.max) > 1e-9 * span) {
|
||||
x_equal = 0;
|
||||
}
|
||||
if (std::fabs(r.plot->cursor(r.cursor_a).x - ref_a) > 1e-9 * span) aligned = 0;
|
||||
if (std::fabs(r.plot->cursor(r.cursor_b).x - ref_b) > 1e-9 * span) aligned = 0;
|
||||
}
|
||||
}
|
||||
std::string traces;
|
||||
for (int p = 0; p < rs.row_count(); ++p) {
|
||||
traces += p == 0 ? "" : "|";
|
||||
traces += rs.plot_label(p) + ":";
|
||||
bool any = false;
|
||||
const RowSet::Row& row = rs.row(p);
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||
const std::string& n = row.signal_name[static_cast<std::size_t>(s)];
|
||||
if (n.empty()) continue;
|
||||
traces += (any ? "," : "") + n;
|
||||
any = true;
|
||||
}
|
||||
if (!any) traces += "-";
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[split-probe] %-14s plots=%d frows=%d cells=[%s] widths=[%s] tile=%d x_equal=%d "
|
||||
"cursors_aligned=%d weights=[%s] traces=[%s]\n",
|
||||
tag, rs.row_count(), frows, cells_s.c_str(), widths_s.c_str(), tile, x_equal,
|
||||
aligned, weights_s.c_str(), traces.c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// 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());
|
||||
a.relayout_pending = true;
|
||||
}
|
||||
|
||||
/// Channel `c` onto plot `p` (the first free slot), by the same name/colour route a drop takes.
|
||||
void split_probe_assign(App& a, int p, std::size_t c) {
|
||||
if (c >= a.model->channel_count() || p < 0 || p >= a.rows->row_count()) return;
|
||||
const ChannelInfo& ch = a.model->channels()[c];
|
||||
a.rows->assign(p, 0, ch.name, ch.name, signal_color_index(a.model->signals(), ch.signal));
|
||||
}
|
||||
|
||||
void split_probe_clear_all(App& a) {
|
||||
for (int r = 0; r < RowSet::kMaxRows; ++r) {
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) a.rows->assign(r, s, "", "", 0);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void split_probe_next(App& a) {
|
||||
++a.dbg_split_step;
|
||||
g_timeout_add(250, split_probe_step, &a);
|
||||
}
|
||||
|
||||
gboolean split_probe_step(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr || a->rows == nullptr) return G_SOURCE_REMOVE;
|
||||
RowSet& rs = *a->rows;
|
||||
switch (a->dbg_split_step) {
|
||||
case 0: {
|
||||
rs.set_cells_per_row({1});
|
||||
split_probe_clear_all(*a);
|
||||
// The `enable_all_probe` assignment, in one call and onto plot 0's slots.
|
||||
std::vector<std::size_t> all(a->model->signals().size());
|
||||
for (std::size_t i = 0; i < all.size(); ++i) all[i] = i;
|
||||
a->model->set_signals_enabled(all, true);
|
||||
int slot = 0;
|
||||
for (std::size_t c = 0; c < a->model->channel_count() && slot < RowSet::kSlotsPerRow; ++c) {
|
||||
const ChannelInfo& ch = a->model->channels()[c];
|
||||
rs.assign(0, slot++, ch.name, ch.name, signal_color_index(a->model->signals(), ch.signal));
|
||||
}
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
split_probe_report(*a, "start");
|
||||
rs.insert_cell(0, 1);
|
||||
split_probe_clear_all(*a);
|
||||
split_probe_assign(*a, 0, 0);
|
||||
split_probe_assign(*a, 1, 1);
|
||||
rs.set_cursors_visible(true);
|
||||
rs.reset_cursors();
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 2:
|
||||
split_probe_report(*a, "split");
|
||||
rs.set_cell_weights(0, {3.0f, 1.0f});
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 3:
|
||||
split_probe_report(*a, "weights 3:1");
|
||||
rs.insert_row(1);
|
||||
rs.insert_cell(1, 1);
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 4: {
|
||||
split_probe_report(*a, "row+cell");
|
||||
// Fill row 0 to its cap, then keep adding rows to the plot cap; the first refusal of each kind
|
||||
// is what is being read.
|
||||
std::string cell_calls;
|
||||
int refused_cell = 0;
|
||||
for (int i = 0; i < RowSet::kMaxCellsPerRow + 1; ++i) {
|
||||
const bool ok = rs.insert_cell(0, rs.cells_in_row(0));
|
||||
cell_calls += (i == 0 ? "" : ",") + std::to_string(ok ? 1 : 0);
|
||||
if (!ok) { ++refused_cell; break; }
|
||||
}
|
||||
std::string row_calls;
|
||||
int refused_row = 0;
|
||||
for (int i = 0; i < RowSet::kMaxRows; ++i) {
|
||||
const bool ok = rs.insert_row(rs.figure_row_count());
|
||||
row_calls += (i == 0 ? "" : ",") + std::to_string(ok ? 1 : 0);
|
||||
if (!ok) { ++refused_row; break; }
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[split-probe] caps-calls insert_cell=[%s] refused_cell=%d insert_row=[%s] "
|
||||
"refused_row=%d plots=%d\n",
|
||||
cell_calls.c_str(), refused_cell, row_calls.c_str(), refused_row, rs.row_count());
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
split_probe_report(*a, "caps");
|
||||
rs.remove_cell(rs.first_plot_of_row(0) + rs.cells_in_row(0) - 1);
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
default:
|
||||
split_probe_report(*a, "remove");
|
||||
break;
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean enable_all_probe(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
// In one call, the way a restored session does it: enabling them one at a time re-plans
|
||||
|
||||
Reference in new issue
Block a user