fix: only a window change sets the time scale; a fit states the whole of it

The time scale is more than the axis: it is also the trigger's event span, the
buffer partition and the control's readout. A zoom or a pan was already clean --
the controller writes the member axis and `clamp_to_data()` caps the span -- but
both fits moved `window_s_` alone and only restated the control. The event span
then stayed on the previous window until the next decade step or unit change
applied the fitted value for real, so one action changed the time scale twice.

`apply_window_scale()` is split out of `apply_time_window()` for the fits to use:
it moves the partition, the event span, the control and the session, and leaves
the view alone, because `set_time_window()` re-arms the live follow and would
throw a held event or a paused view back onto the stream. The fits report the
span they framed, `fit_all()` delegates its x half to `fit_x()` so the two cannot
drift, and the probe drives the menu handlers rather than `RowSet` so it sees the
half that lives there. `[fit-probe]` gained `event_span=`, which must equal
`window=` on every line.

Also moves the plotter pin to 03c41ad (three build warnings, no behaviour).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-05 16:12:59 +02:00
1 parent 1f6738031a
commit 9888fd9977
7 files changed
+128 -63

No files matched your search

+17 -26
View File
@@ -657,21 +657,24 @@ void RowSet::unpin_pause_window() {
set_time_window(window_s_);
}
void RowSet::fit_x() {
bool RowSet::fit_x(double& span) {
Row& first = row(0);
if (!first.plot || !figure_) return;
if (!first.plot || !figure_) return false;
first.plot->auto_fit_x();
const plot::Range fitted = first.plot->x_range(first.axis_x);
const double span = fitted.span();
if (!std::isfinite(span) || !(span > 0.0)) return;
// The same two paths `fit_all()` takes: a held event owns its window and states it
// through the link group, a live view re-arms the follow and frames the newest span.
const double fitted_span = fitted.span();
if (!std::isfinite(fitted_span) || !(fitted_span > 0.0)) return false;
if (pinned_) {
window_s_ = span;
// Fitting a captured or paused view: the window is the snapshot's own span, and
// `set_time_window()` would re-arm the follow and re-frame on a live edge that a frozen
// view has nothing bound to.
window_s_ = fitted_span;
figure_->set_x_link_range(x_group_, fitted);
} else {
set_time_window(span);
set_time_window(fitted_span);
}
span = window_s_;
return true;
}
void RowSet::sync_cursors(int drag_row) {
@@ -785,28 +788,16 @@ void RowSet::pin_event_window(double pre_s, double post_s) {
figure_->set_x_link_range(x_group_, {-pre_s, post_s});
}
void RowSet::fit_all() {
bool RowSet::fit_all(double& span) {
for (int i = 0; i < rows_; ++i) {
Row& r = row(i);
if (r.plot) r.plot->auto_fit_y(r.axis_y);
}
Row& first = row(0);
if (!first.plot || !figure_) return;
// Row 0 fits its own x to the data it holds; that span becomes the shared window, and
// stating it through the group is what puts every row on it.
first.plot->auto_fit_x();
const plot::Range fitted = first.plot->x_range(first.axis_x);
const double span = fitted.span();
if (!std::isfinite(span) || !(span > 0.0)) return;
if (pinned_) {
// Fitting a captured event: the window is the snapshot's own `[-pre, +post]` span,
// and `set_time_window()` would re-arm the follow and re-frame on a live edge that
// trigger mode has nothing bound to.
window_s_ = span;
figure_->set_x_link_range(x_group_, fitted);
} else {
set_time_window(span);
}
// The x half is `fit_x()`'s exactly -- row 0's data span, stated through the link group so
// every row lands on it -- and sharing it is what keeps the two fits from drifting apart.
// The y fit above has already happened either way: a row 0 that holds nothing still gets
// every row's y scaled, it just has no span to report.
return fit_x(span);
}
void RowSet::note_y_gutter(double need_px) {