Gated measurements #2

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

No files matched your search

+5 -1
View File
@@ -129,7 +129,11 @@ rect plus a `kMaximizeRevealPx` of slack, per row) and is invisible otherwise, s
decorated by controls nobody is reaching for; one click makes that row fill
the plot area and the same button restores the whole arrangement; the plot's right-click menu
offers the same as **Full screen this figure** / **Restore the layout**, and a layout of a single
figure carries no button (there is nothing to enlarge *to*). It is a *view* — the row set
figure carries no button (there is nothing to enlarge *to*). Beside the toggle, and with the
same show-on-approach, a **detach** button moves the figure into a window of its own (removed
from the main window's rows, which save the change); the detached window carries an **attach**
button in the same corner — its figure returns to the main window's rows and the window closes.
It is a *view* — the row set
saves every row's state on the way in and re-applies it on the way out (`RowState` in
`plot_host.*`), so nothing plotted moves and nothing is saved. A plot's right-click menu has
**Open this plot in a new window**, which copies that figure — its cells, their width shares and
+11 -2
View File
@@ -1422,19 +1422,28 @@ Measured output (viewport pixels; the absolute numbers are the headless window's
## `[maximize-probe]`
`UDPSCOPE_PROBE_MAXIMIZE_MS`. The one-click **full-screen row** toggle, exercised through the
`RowSet` call the button's handler makes. **Do not run it with `UDPSCOPE_ENABLE_ALL_MS`**: the
enable-all probe's second leg clears the front plot two seconds in, and this probe's `before`
read then names an empty row (`row0=`) -- a probe race, not a defect. The full-screen recipe:
`RowSet` call the button's handler makes (a probe cannot click an overlay button the frame
places), arranged as two single-cell rows with one signal each. The two reads after it are the
**corner reveal**: no pointer over the area, then a pointer position in the first row's corner
(set directly -- the probe owns the state the motion controller fills). Measured output:
```
[maximize-probe] single_buttons=0 before: rows=2 frows=2 row0=sine row1=square buttons=2; max: rows=1 frows=1 flag=1 row0=sine; back: rows=2 row0=sine row1=square flag=0
[maximize-probe] corner: hidden_when_off=1 shown_when_near=1 other_row=0
[maximize-probe] single_buttons=0 detach_single=0 before: rows=2 frows=2 row0=sine row1=square buttons=2 detach=2; max: rows=1 frows=1 flag=1 row0=sine; back: rows=2 row0=sine row1=square flag=0
[maximize-probe] corner: hidden_when_off=1 shown_when_near=1 other_row=0 detach_near=1 detach_other=0
```
- `single_buttons=` is the toggle count *before* the second row is added, while the layout is
the default one figure: **0** — one figure has nothing to enlarge *to*, so it carries no
button (and `buttons=` is what the same count reads once the second row exists).
`detach_single=` is the same for the **detach** buttons: **0** — detaching the only figure
would leave the main window with nothing to draw — and `detach=2` once the second row exists
(and **0** again on the `max:` line, where a maximized layout has hidden its rows).
- the corner leg's `detach_near=1 detach_other=0` read the detach buttons with the same pointer
the maximize toggle reads: both come out together at the corner, the other row's stay hidden.
The pointer sits on the detach button's own centre — its zone is the strictest of the pair.
- `before:` is the arrangement as set: two plots, two figure rows, the signals on their rows,
and `buttons=` one overlay toggle per figure row (the toggles are on screen whether or not
the layout editor is up).
+4 -1
View File
@@ -77,7 +77,10 @@
nothing is saved, nothing is dropped, and the rows' traces, colours and cursors come back
where they were. The plot's right-click menu offers the same as <gui>Full screen this
figure</gui> (and <gui>Restore the layout</gui> while one is enlarged). A layout of a single
figure has nothing to enlarge *to*, so it carries no button.</p></item>
figure has nothing to enlarge *to*, so it carries no button.</p></item> Beside the toggle, shown the same way, a <gui>detach</gui> button moves the figure into a
window of its own — it leaves the main window's rows; the detached window carries an
<gui>attach</gui> button in the same corner, which returns the figure to the main window and
closes the window.
<item><p>A plot's right-click menu has <gui>Open this plot in a new window</gui>: a second
window showing the same stream through a copy of that figure (its cells, their widths and
every trace), with its own axes following the main window's time window. The overflow menu's
+195 -5
View File
@@ -583,7 +583,7 @@ void relayout_areas(App& app) {
/// about the data or the display mode. `figure_row` names the main window's figure to copy --
/// its cells, their width shares and every slot's signal in its colour -- because "open this
/// one big" is the reason a user reaches for a second window; -1 opens an empty scope.
void open_scope_window(App& app, int figure_row) {
void open_scope_window(App& app, int figure_row, bool detached) {
if (figure_row >= app.rows->figure_row_count()) figure_row = -1;
auto scope = std::make_unique<App::ScopeWindow>();
scope->rows = std::make_unique<RowSet>();
@@ -621,13 +621,60 @@ void open_scope_window(App& app, int figure_row) {
g_object_set_data(G_OBJECT(scope->drop), "udpscope-scope", scope.get());
g_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app);
gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(scope->drop));
scope->detached = detached;
scope->window = GTK_WINDOW(gtk_window_new());
const std::string title = figure_row >= 0
? "udpscope \u2014 plot " + std::to_string(figure_row + 1)
: std::string("udpscope \u2014 scope");
std::string title = figure_row >= 0
? "udpscope \u2014 plot " + std::to_string(figure_row + 1)
: std::string("udpscope \u2014 scope");
if (detached) title += " (detached)";
gtk_window_set_title(scope->window, title.c_str());
gtk_window_set_default_size(scope->window, 900, 500);
gtk_window_set_child(scope->window, scope->widget);
if (detached) {
// The attach control, and it is the main window's own reveal mechanism in miniature: an
// overlay wrapping the GL area (so a revealed button sits *under* the pointer and the motion
// event still travels the ancestor -- a leave on the GL widget would fire the moment the
// button appeared and hide it again), and one button in the top-right corner, hidden until
// the pointer nears it. Only a detached window carries one: a copy or an empty window has
// nothing to put back.
scope->attach_overlay = gtk_overlay_new();
gtk_overlay_set_child(GTK_OVERLAY(scope->attach_overlay), scope->widget);
gtk_window_set_child(scope->window, scope->attach_overlay);
GtkEventController* motion = gtk_event_controller_motion_new();
g_signal_connect(motion, "motion", G_CALLBACK(+[](GtkEventControllerMotion*, double x,
double y, gpointer user) {
App::ScopeWindow* scope = static_cast<App::ScopeWindow*>(user);
scope->pointer_x = x;
scope->pointer_y = y;
scope->pointer_valid = true;
}),
scope.get());
g_signal_connect(motion, "leave", G_CALLBACK(+[](GtkEventControllerMotion*, gpointer user) {
static_cast<App::ScopeWindow*>(user)->pointer_valid = false;
}),
scope.get());
gtk_widget_add_controller(scope->attach_overlay, GTK_EVENT_CONTROLLER(motion));
GtkWidget* attach = gtk_button_new_from_icon_name("view-restore-symbolic");
gtk_widget_set_tooltip_text(attach, "attach this figure back to the main window");
gtk_widget_add_css_class(attach, "circular");
gtk_widget_add_css_class(attach, "osd");
gtk_widget_set_size_request(attach, kLayoutButtonPx, kLayoutButtonPx);
gtk_widget_set_visible(attach, FALSE);
g_object_set_data(G_OBJECT(attach), "udpscope-scope", scope.get());
g_signal_connect(attach, "clicked", G_CALLBACK(+[](GtkButton* b, gpointer user) {
App& a = *static_cast<App*>(user);
auto* scope =
static_cast<App::ScopeWindow*>(g_object_get_data(G_OBJECT(b),
"udpscope-scope"));
if (scope == nullptr) return;
outlive_the_rebuild(GTK_WIDGET(b));
attach_scope_window(a, *scope);
}),
&app);
gtk_overlay_add_overlay(GTK_OVERLAY(scope->attach_overlay), attach);
scope->attach_buttons.push_back(attach);
} else {
gtk_window_set_child(scope->window, scope->widget);
}
if (GtkApplication* application = gtk_window_get_application(GTK_WINDOW(app.window))) {
gtk_window_set_application(scope->window, application);
}
@@ -1025,6 +1072,7 @@ GtkWidget* layout_overlay_button(App& app, const char* icon, const char* tooltip
/// Forward declarations: the toggles' handlers are defined after the builder that wires them.
void on_maximize_clicked(GtkButton* b, gpointer user);
void on_detach_clicked(GtkButton* b, gpointer user);
void on_fullscreen_row(GtkButton* b, gpointer user);
void rebuild_maximize_buttons(App& app);
@@ -1052,6 +1100,10 @@ void rebuild_maximize_buttons(App& app) {
gtk_overlay_remove_overlay(GTK_OVERLAY(app.area_overlay), b);
}
app.maximize_buttons.clear();
for (GtkWidget* b : app.detach_buttons) {
gtk_overlay_remove_overlay(GTK_OVERLAY(app.area_overlay), b);
}
app.detach_buttons.clear();
app.maximize_buttons_rows = app.rows->figure_row_count();
const bool maximized = app.rows->figures_maximized();
if (!maximized && app.maximize_buttons_rows <= 1) return;
@@ -1063,6 +1115,17 @@ void rebuild_maximize_buttons(App& app) {
app.maximize_buttons);
g_object_set_data(G_OBJECT(b), kMaximizeKey, GINT_TO_POINTER(fr + 1));
g_signal_connect(b, "clicked", G_CALLBACK(on_maximize_clicked), &app);
// The detach button sits beside it, one rule behind it: a layout of one figure detaches
// nothing (the main window would be left with nothing to draw), and a maximized one has
// hidden its own rows anyway.
if (!maximized && app.maximize_buttons_rows >= 2) {
GtkWidget* d = overlay_button(app, "send-to-symbolic",
"detach this figure to a window of its own",
app.detach_buttons);
gtk_widget_set_name(d, "udpscope-detach-button");
g_object_set_data(G_OBJECT(d), kMaximizeKey, GINT_TO_POINTER(fr + 1));
g_signal_connect(d, "clicked", G_CALLBACK(on_detach_clicked), &app);
}
}
}
@@ -1114,6 +1177,38 @@ void sync_maximize_buttons(App& app) {
app.area_pointer_y <= by + kLayoutButtonPx + kMaximizeRevealPx;
gtk_widget_set_visible(b, revealed);
}
// The detach buttons, one per figure row to the **left** of its full-screen toggle: the same
// corner, the same reveal, the row's own rect -- a pointer arriving for one of the pair shows
// both, and leaving hides both.
for (GtkWidget* b : app.detach_buttons) {
const int fr = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(b), kMaximizeKey)) - 1;
if (fr < 0 || fr >= frows || fr >= static_cast<int>(fl.rows.size())) {
gtk_widget_set_visible(b, FALSE);
continue;
}
const int first = app.rows->first_plot_of_row(fr);
const int n = app.rows->cells_in_row(fr);
const plot::Plot* last = n > 0 ? app.rows->row(first + n - 1).plot : nullptr;
const double right =
last != nullptr
? static_cast<double>(last->layout().axes.right()) / scale
: static_cast<double>(fl.rows[static_cast<std::size_t>(fr)].right()) / scale;
const double top =
static_cast<double>(fl.rows[static_cast<std::size_t>(fr)].y) / scale + kLayoutButtonPad;
gtk_widget_set_halign(b, GTK_ALIGN_START);
gtk_widget_set_valign(b, GTK_ALIGN_START);
const double dx = std::clamp(right - 2 * kLayoutButtonPx - 2 * kLayoutButtonPad, 0.0,
std::max(0.0, width - kLayoutButtonPx));
gtk_widget_set_margin_start(b, static_cast<int>(dx));
const double dy = std::clamp(top, 0.0, std::max(0.0, height - kLayoutButtonPx));
gtk_widget_set_margin_top(b, static_cast<int>(dy));
const bool revealed =
app.area_pointer_valid && app.area_pointer_x >= dx - kMaximizeRevealPx &&
app.area_pointer_x <= dx + kLayoutButtonPx + kMaximizeRevealPx &&
app.area_pointer_y >= dy - kMaximizeRevealPx &&
app.area_pointer_y <= dy + kLayoutButtonPx + kMaximizeRevealPx;
gtk_widget_set_visible(b, revealed);
}
}
/// The row's toggle: the `kMaximizeKey` data names the figure row it sits on.
@@ -1131,6 +1226,100 @@ void on_fullscreen_row(GtkButton* b, gpointer user) {
if (popover != nullptr) gtk_popover_popdown(popover);
}
/// The detach button, the full-screen toggle's neighbour: the figure row it names moves to a
/// window of its own.
void on_detach_clicked(GtkButton* b, gpointer user) {
App& app = *static_cast<App*>(user);
detach_figure(app, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(b), kMaximizeKey)) - 1);
}
/// The attach control's reveal and placement, one call per frame from the tick: the button sits at
/// the window's top-right corner and comes out of hiding while the pointer is near it -- the
/// placed rect inflated by `kMaximizeRevealPx`, the zone and the button measured from the same
/// numbers so they cannot disagree.
void sync_scope_attach(App& app, App::ScopeWindow& scope) {
(void)app;
if (scope.attach_overlay == nullptr || scope.attach_buttons.empty() || scope.widget == nullptr) {
return;
}
const double scale = scope.area != nullptr && scope.area->device_scale() > 0.0f
? static_cast<double>(scope.area->device_scale())
: 1.0;
(void)scale;
const double width = static_cast<double>(gtk_widget_get_width(scope.widget));
const double height = static_cast<double>(gtk_widget_get_height(scope.widget));
for (GtkWidget* b : scope.attach_buttons) {
const double bx = std::clamp(static_cast<double>(width - kLayoutButtonPx - kLayoutButtonPad),
0.0, std::max(0.0, width - kLayoutButtonPx));
const double by = std::clamp(static_cast<double>(kLayoutButtonPad), 0.0,
std::max(0.0, height - kLayoutButtonPx));
gtk_widget_set_halign(b, GTK_ALIGN_START);
gtk_widget_set_valign(b, GTK_ALIGN_START);
gtk_widget_set_margin_start(b, static_cast<int>(bx));
gtk_widget_set_margin_top(b, static_cast<int>(by));
const bool revealed =
scope.pointer_valid && scope.pointer_x >= bx - kMaximizeRevealPx &&
scope.pointer_x <= bx + kLayoutButtonPx + kMaximizeRevealPx &&
scope.pointer_y >= by - kMaximizeRevealPx &&
scope.pointer_y <= by + kLayoutButtonPx + kMaximizeRevealPx;
gtk_widget_set_visible(b, revealed);
}
}
/// **Detach**: the figure row moves to a window of its own. Copied first (the new window opens
/// carrying exactly what was on screen), then removed from the main window's rows -- the copy and
/// the removal are one action in one call, so nothing renders in between. A detached window is the
/// one kind with an attach control; the arrangement the user had follows it there.
void detach_figure(App& app, int fr) {
if (app.rows->figures_maximized() || app.rows->figure_row_count() <= 1) return;
if (fr < 0 || fr >= app.rows->figure_row_count()) return;
const std::string label = app.rows->plot_label(app.rows->first_plot_of_row(fr));
open_scope_window(app, fr, /*detached=*/true);
if (!app.rows->remove_row(fr)) return;
if (app.area != nullptr) app.area->set_figure(&app.rows->figure());
rebuild_maximize_buttons(app);
rebuild_signal_panels(app);
if (app.layout_editing) rebuild_layout_editor(app);
schedule_layout_save(app);
set_notice(app, "plot " + label + " detached to its own window", 4);
}
/// **Attach**: a detached window's figure row is appended to the main window's -- the window's
/// cells, their weights and every slot's signal in its colour -- and the window closes. Closing
/// runs its own teardown (the pages' *Plot on* groups are refreshed before the window's struct is
/// freed), so nothing here may touch `scope` once the destroy has been asked for.
void attach_scope_window(App& app, App::ScopeWindow& scope) {
if (!scope.detached || scope.rows == nullptr || scope.rows->row_count() <= 0) return;
const int at = app.rows->figure_row_count();
if (!app.rows->insert_row(at)) {
set_notice(app, "no room for another figure row", 4);
return;
}
const int cells = scope.rows->cells_in_row(0);
for (int k = 1; k < cells; ++k) {
if (!app.rows->insert_cell(at, k)) break;
}
if (cells > 1) app.rows->set_cell_weights(at, scope.rows->cell_weights(0));
const int first = app.rows->first_plot_of_row(at);
for (int k = 0; k < cells; ++k) {
const RowSet::Row& src = scope.rows->row(k);
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
const std::size_t si = static_cast<std::size_t>(s);
if (src.signal_name[si].empty()) continue;
app.rows->assign(first + k, s, src.signal_name[si], src.name[si], src.color_index[si]);
}
}
GtkWindow* window = scope.window;
if (window != nullptr) gtk_window_destroy(window); // erases the scope, refreshes the pages
app.relayout_pending = true;
if (app.area != nullptr) app.area->set_figure(&app.rows->figure());
rebuild_maximize_buttons(app);
rebuild_signal_panels(app);
if (app.layout_editing) rebuild_layout_editor(app);
schedule_layout_save(app);
set_notice(app, "the detached figure is back on the main window", 4);
}
/// The plot menu's way into the layout editor: the same toggle the overflow menu's `Layout editor`
/// entry flips, so the menu cannot drift from the button's own state -- and the place a user who is
/// looking at the plot will find splitting.
@@ -4255,6 +4444,7 @@ gboolean tick(gpointer user) {
if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) {
scope->area->set_figure(&scope->rows->figure());
}
if (scope->detached) sync_scope_attach(*app, *scope);
if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget));
}
}
+27 -1
View File
@@ -221,6 +221,18 @@ struct App {
/// The drop target wired onto `widget`: the handler reads the *window* off it (each window's
/// target names its own scope), and the probe calls the handler with it directly.
GtkDropTarget* drop = nullptr;
/// This window came from a **detach** -- the figure left the main window to live here -- so it
/// carries the attach control and can be put back. A copied or empty window is a plain second
/// view, and has no attach control.
bool detached = false;
/// The attach control: an overlay wrapping the GL widget, its button in the top-right corner
/// (revealed by pointer proximity, the same rule the main window's toggles follow), and the
/// pointer state the overlay's own motion controller keeps.
GtkWidget* attach_overlay = nullptr;
std::vector<GtkWidget*> attach_buttons;
double pointer_x = 0.0;
double pointer_y = 0.0;
bool pointer_valid = false;
/// 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
/// one plot the moment the figure mode is ever left.
@@ -243,6 +255,10 @@ struct App {
/// count changes (`maximize_buttons_rows` is the count they were built for).
std::vector<GtkWidget*> maximize_buttons;
int maximize_buttons_rows = -1;
/// The **detach** buttons: one per figure row beside its full-screen toggle, same reveal,
/// same rebuild cadence -- but only while the layout holds two or more figures (detaching the
/// only figure would leave the main window with nothing to draw) and never while maximized.
std::vector<GtkWidget*> detach_buttons;
/// The boundary a resize drag is on, and the weights it started from: a drag is *relative* to where it
/// began, so the beginning has to be kept -- reading the weights while dragging would compound.
int layout_drag_row = -1;
@@ -878,6 +894,7 @@ struct App {
int dbg_maximize_step = 0;
std::size_t dbg_maximize_single = 0;
int dbg_maximize_hidden = -1;
std::size_t dbg_detach_single = 0;
/// Which step of the scope-window probe runs next (`UDPSCOPE_PROBE_SCOPE_MS`).
int dbg_scope_step = 0;
int cursors_pane_mode = -1;
@@ -2161,7 +2178,16 @@ gboolean scope_probe_step(gpointer user);
/// A second top-level window showing the same stream through its own figure: `figure_row` >= 0
/// copies that figure of the main window (cells, widths, every slot's signal), -1 opens an
/// empty one-row scope. The main window's tick binds, acquires and repaints it.
void open_scope_window(App& app, int figure_row);
void open_scope_window(App& app, int figure_row, bool detached = false);
/// Detaches figure row `fr` into a window of its own: the figure is copied into a new scope
/// window -- which is what makes the window *detached*, the one kind with an attach control --
/// and removed from the main window's rows. Refused, changing nothing, for a one-figure layout
/// or a maximized one: detaching then would leave the main window with nothing to draw.
void detach_figure(App& app, int fr);
/// Puts a detached window's figure back: a figure row is appended to the main window carrying
/// the window's arrangement (cells, weights, every slot's signal in its colour) and the window
/// closes. The window must be a detached one; anything else returns without changing.
void attach_scope_window(App& app, App::ScopeWindow& scope);
gboolean measurepin_probe(gpointer user);
gboolean measurepin_held_wait(gpointer user);
gboolean measurepin_pause_wait(gpointer user);
+59 -16
View File
@@ -1651,6 +1651,7 @@ gboolean maximize_probe_step(gpointer user) {
// A layout of one figure carries no full-screen toggle (there is nothing to enlarge
// *to*); the button appears together with the second figure row, below.
a->dbg_maximize_single = a->maximize_buttons.size();
a->dbg_detach_single = a->detach_buttons.size();
a->rows->set_cells_per_row({1, 1});
a->rows->assign(0, 0, sine, sine, sine_color);
a->rows->assign(1, 0, square, square, square_color);
@@ -1684,11 +1685,13 @@ gboolean maximize_probe_step(gpointer user) {
const std::string row1_back = a->rows->row(1).signal_name[0];
const bool flag_back = a->rows->figures_maximized();
std::fprintf(stderr,
"[maximize-probe] single_buttons=%zu before: rows=%d frows=%d row0=%s row1=%s "
"buttons=%zu; max: rows=%d frows=%d flag=%d row0=%s; "
"[maximize-probe] single_buttons=%zu detach_single=%zu before: rows=%d frows=%d "
"row0=%s row1=%s buttons=%zu detach=%zu; max: rows=%d frows=%d flag=%d row0=%s; "
"back: rows=%d row0=%s row1=%s flag=%d\n",
a->dbg_maximize_single, rows_before, frows_before, row0_before.c_str(),
row1_before.c_str(), buttons, rows_max, frows_max, flag_max ? 1 : 0,
a->dbg_maximize_single, a->dbg_detach_single, rows_before, frows_before,
row0_before.c_str(),
row1_before.c_str(), buttons, a->detach_buttons.size(), rows_max, frows_max,
flag_max ? 1 : 0,
row0_max.c_str(), rows_back, row0_back.c_str(), row1_back.c_str(),
flag_back ? 1 : 0);
std::fflush(stderr);
@@ -1703,25 +1706,37 @@ gboolean maximize_probe_step(gpointer user) {
case 2: {
a->dbg_maximize_hidden =
a->maximize_buttons.empty() || !gtk_widget_get_visible(a->maximize_buttons[0]) ? 1 : 0;
a->area_pointer_x = static_cast<double>(gtk_widget_get_width(a->area_widget)) - 8.0;
a->area_pointer_y = 8.0;
// On the **detach** button's own centre -- its zone is the strictest of the corner pair, so a
// pointer there must reveal both it and the full-screen toggle beside it.
if (const plot::Plot* last = a->rows->row(0).plot) {
const double scale =
a->area != nullptr && a->area->device_scale() > 0.0f
? static_cast<double>(a->area->device_scale())
: 1.0;
const double right = static_cast<double>(last->layout().axes.right()) / scale;
a->area_pointer_x = right - 2 * kLayoutButtonPx - 2 * kLayoutButtonPad +
0.5 * kLayoutButtonPx;
a->area_pointer_y = kLayoutButtonPad + 0.5 * kLayoutButtonPx;
} else {
a->area_pointer_x = static_cast<double>(gtk_widget_get_width(a->area_widget)) - 8.0;
a->area_pointer_y = 8.0;
}
a->area_pointer_valid = true;
a->dbg_maximize_step = 3;
g_timeout_add(250, maximize_probe_step, a);
break;
}
case 3: {
const int shown = !a->maximize_buttons.empty() &&
gtk_widget_get_visible(a->maximize_buttons[0])
? 1
: 0;
const int other = a->maximize_buttons.size() > 1 &&
gtk_widget_get_visible(a->maximize_buttons[1])
? 1
: 0;
const auto visible = [](const std::vector<GtkWidget*>& buttons, std::size_t i) {
return i < buttons.size() && gtk_widget_get_visible(buttons[i]) ? 1 : 0;
};
const int shown = visible(a->maximize_buttons, 0);
const int other = visible(a->maximize_buttons, 1);
const int dshown = visible(a->detach_buttons, 0);
const int dother = visible(a->detach_buttons, 1);
std::fprintf(stderr, "[maximize-probe] corner: hidden_when_off=%d shown_when_near=%d "
"other_row=%d\n",
a->dbg_maximize_hidden, shown, other);
"other_row=%d detach_near=%d detach_other=%d\n",
a->dbg_maximize_hidden, shown, other, dshown, dother);
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
@@ -1824,6 +1839,34 @@ gboolean scope_probe_step(gpointer user) {
std::fprintf(stderr,
"[scope-probe] drop=%d slot1='%s' page_groups=%d\n",
dropped ? 1 : 0, slot1.c_str(), groups);
// **Detach and attach**: a second figure row detaches -- the window is marked detached and
// carries the attach control -- and its figure comes back on the main window.
a->rows->set_cells_per_row({1, 1});
{
std::size_t ramp = std::size_t(-1);
int color = 0;
for (std::size_t i = 0; i < a->model->signals().size(); ++i) {
if (a->model->signals()[i].name == "ramp") {
ramp = i;
color = signal_color_index(a->model->signals(), i);
}
}
if (ramp != std::size_t(-1)) a->rows->assign(1, 0, "ramp", "ramp", color);
}
a->relayout_pending = true;
detach_figure(*a, 1);
const bool detached_ok = a->scopes.size() == 3 && a->scopes[2]->detached &&
a->rows->figure_row_count() == 1 &&
a->scopes[2]->rows->row(0).signal_name[0] == "ramp" &&
a->scopes[2]->attach_buttons.size() == 1;
attach_scope_window(*a, *a->scopes[2]);
const int frows_back = a->rows->figure_row_count();
const bool attach_ok = a->scopes.size() == 2 && frows_back == 2 &&
a->rows->row(a->rows->first_plot_of_row(1)).signal_name[0] == "ramp";
std::fprintf(stderr,
"[scope-probe] detach=%d scopes=3 frows=1 attach=%d scopes_back=2 "
"frows_back=%d slot='ramp'\n",
detached_ok ? 1 : 0, attach_ok ? 1 : 0, frows_back);
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
+2 -2
View File
@@ -443,8 +443,8 @@ constexpr TourStep kTourSteps[] = {
{"plot", nullptr, "The plot",
"Every signal you have switched on is drawn here, all plots sharing one time axis -- and the "
"plots of a split row share one y as well. The wheel zooms, a drag pans, and a right-click on "
"a plot opens its own menu: fit, full screen (the button also comes out in a figure's corner "
"as the pointer nears it), a second window showing that figure, and the layout editor, which "
"a plot opens its own menu: fit, full screen and detach (the corner buttons come out as the "
"pointer nears them), a second window showing that figure, and the layout editor, which "
"splits a row into up to four plots.",
&App::area_overlay},
{"badges", nullptr, "The badges",