Gated measurements #2

Merged
martino merged 12 commits from gated-measurements into main 2026-11-06 12:30:43 +01:00
3 changed files with 64 additions and 7 deletions
Showing only changes of commit fa2d0f12fc - Show all commits

No files matched your search

+5 -2
View File
@@ -146,8 +146,11 @@ mode or the span. **A signal badge dragged onto a scope window's plot lands ther
drop the main plot takes, into that window's own row set, on the cell the pointer is over — and drop the main plot takes, into that window's own row set, on the cell the pointer is over — and
the signal page's *Plot on* buttons list **every window**: one group per window (named, `In the signal page's *Plot on* buttons list **every window**: one group per window (named, `In
"plot 2"`), one button per plot of it. A right-click on a scope window's plot opens **its own "plot 2"`), one button per plot of it. A right-click on a scope window's plot opens **its own
menu** — y axis (auto or a fixed range), fit, remove a trace — acting on that window's rows; a menu** — y axis (auto or a fixed range), *Fit X axis*, *Fit all*, remove a trace — acting on that
detached window's menu carries **Attach back to the main window** beside them. window's rows (the x fit resets the **shared** time span, so every view follows); a detached
window's menu carries **Attach back to the main window** beside them. A window that cannot paint
says so in its own title after two seconds (and on stderr): a blank plot is indistinguishable from
a context that failed, and the sentence names which.
mode or the span. mode or the span.
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left **The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
+55 -5
View File
@@ -3711,15 +3711,38 @@ void on_scope_y_fixed_clicked(GtkButton* b, gpointer user) {
scope_menu_dismiss(b); scope_menu_dismiss(b);
} }
/// The y fit of a scope window: the window's x follows the main window's span, so the fit is the /// The y fit of a scope window: the y axes alone.
/// y axes alone -- `fit_all()`'s x reset would be undone by the next frame's mirror. void on_scope_fit_y_clicked(GtkButton* b, gpointer) {
void on_scope_fit_clicked(GtkButton* b, gpointer) {
App::ScopeWindow* scope = scope_menu_scope(b); App::ScopeWindow* scope = scope_menu_scope(b);
if (scope == nullptr || scope->rows == nullptr) return; if (scope == nullptr || scope->rows == nullptr) return;
scope->rows->set_y_auto(scope_menu_row(b), true); scope->rows->set_y_auto(scope_menu_row(b), true);
scope_menu_dismiss(b); scope_menu_dismiss(b);
} }
/// The x fit of a scope window is the main window's: there is **one** time span for every view,
/// and the scope mirrors it -- so the fit resets the shared x range and the next frame mirrors
/// the result back, in this window and in every other.
void on_scope_fit_x_clicked(GtkButton* b, gpointer user) {
App& app = *static_cast<App*>(user);
app.rows->fit_x();
app.notice = "x back to the " + format_window(app.rows->time_window()) + " window";
app.notice_until_us = g_get_monotonic_time() + 3 * G_TIME_SPAN_SECOND;
scope_menu_dismiss(b);
}
/// Both fits: this window's y, the shared x.
void on_scope_fit_all_clicked(GtkButton* b, gpointer user) {
App& app = *static_cast<App*>(user);
App::ScopeWindow* scope = scope_menu_scope(b);
if (scope == nullptr || scope->rows == nullptr) return;
scope->rows->set_y_auto(scope_menu_row(b), true);
app.rows->fit_x();
app.notice = "y fitted to the data, x back to the " +
format_window(app.rows->time_window()) + " window";
app.notice_until_us = g_get_monotonic_time() + 3 * G_TIME_SPAN_SECOND;
scope_menu_dismiss(b);
}
void on_scope_remove_clicked(GtkButton* b, gpointer) { void on_scope_remove_clicked(GtkButton* b, gpointer) {
App::ScopeWindow* scope = scope_menu_scope(b); App::ScopeWindow* scope = scope_menu_scope(b);
if (scope == nullptr || scope->rows == nullptr) return; if (scope == nullptr || scope->rows == nullptr) return;
@@ -3785,8 +3808,10 @@ void on_scope_right_click(GtkGestureClick* gesture, int, double x, double y, gpo
g_signal_connect(y_auto, "clicked", G_CALLBACK(on_scope_y_auto_clicked), user); g_signal_connect(y_auto, "clicked", G_CALLBACK(on_scope_y_auto_clicked), user);
GtkWidget* y_fixed = row_btn("Y axis: fixed range..."); GtkWidget* y_fixed = row_btn("Y axis: fixed range...");
g_signal_connect(y_fixed, "clicked", G_CALLBACK(on_scope_y_fixed_clicked), user); g_signal_connect(y_fixed, "clicked", G_CALLBACK(on_scope_y_fixed_clicked), user);
GtkWidget* fit = row_btn("Fit Y axis"); GtkWidget* fit_x = row_btn("Fit X axis");
g_signal_connect(fit, "clicked", G_CALLBACK(on_scope_fit_clicked), user); g_signal_connect(fit_x, "clicked", G_CALLBACK(on_scope_fit_x_clicked), user);
GtkWidget* fit = row_btn("Fit all");
g_signal_connect(fit, "clicked", G_CALLBACK(on_scope_fit_all_clicked), user);
// One removal per trace on the plot, flat rather than as a second view: a scope window holds // One removal per trace on the plot, flat rather than as a second view: a scope window holds
// at most its own figure's traces, and the menu is small. // at most its own figure's traces, and the menu is small.
@@ -4610,6 +4635,31 @@ gboolean tick(gpointer user) {
// scope->area->set_figure(&scope->rows->figure()); // scope->area->set_figure(&scope->rows->figure());
// } // }
if (scope->detached) sync_scope_attach(*app, *scope); if (scope->detached) sync_scope_attach(*app, *scope);
// **A window that cannot paint says so.** A blank plot is indistinguishable from a
// context that failed or an area that never got a frame, and the report ("nothing is
// drawn, no axes") is exactly what this turns into a sentence: the window's own title
// carries the reason, and stderr carries it with the window's name.
if (scope->area != nullptr && scope->paint_checks >= 0 && scope->paint_checks < 120) {
++scope->paint_checks;
if (scope->paint_checks == 120) {
const plot::Status st = scope->area->init_status();
const plot::u64 painted = scope->area->frames_painted();
if (!st || painted == 0) {
const std::string why =
st ? std::string("the plot painted no frame in two seconds")
: std::string("GL context failed: ") + st.message;
std::fprintf(stderr, "[scope] window '%s' is not drawing: %s\n",
scope->name.c_str(), why.c_str());
std::fflush(stderr);
if (scope->window != nullptr) {
const char* current = gtk_window_get_title(scope->window);
gtk_window_set_title(scope->window,
((current != nullptr ? std::string(current) + " — " : "") + why)
.c_str());
}
}
}
}
if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget)); if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget));
} }
} }
+4
View File
@@ -233,6 +233,10 @@ struct App {
double pointer_x = 0.0; double pointer_x = 0.0;
double pointer_y = 0.0; double pointer_y = 0.0;
bool pointer_valid = false; bool pointer_valid = false;
/// Frames this window's area was watched for its first paint: a window whose GL context failed
/// or that never paints says so in its own title (and on stderr) instead of staying a blank
/// rectangle nobody can diagnose.
int paint_checks = 0;
/// The area's initial plot, owned here: `PlotArea::create()` wants a `Plot&` that /// The area's initial plot, owned here: `PlotArea::create()` wants a `Plot&` that
/// outlives the widget, and sharing the main window's placeholder would hand two areas /// outlives the widget, and sharing the main window's placeholder would hand two areas
/// one plot the moment the figure mode is ever left. /// one plot the moment the figure mode is ever left.