ui: the sidebar is two tabs, and a toggle disables its pane
Replace the sidebar's five-panel scrolling column with an AdwViewStack carrying two pages — Measure (cursors + measurements) and Signals (trigger + math + channel list) — with an AdwViewSwitcher above it. The trigger and cursors toggles change from "reveal/hide my pane" to "enable/disable my feature, leaving the pane insensitive", following the disable-not-hide principle. The keys probe loses a duplicate registration that was toggling the sidebar back on before the exit probe captured its state; the exit-probe captures now read adw_overlay_split_view_get_show_ sidebar() instead of gtk_widget_get_visible(), which AdwOverlaySplitView never changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e10a92fc52
commit
44d2fd35dc
11 files changed
+223
-228
No files matched your search
+4
-8
@@ -12,14 +12,10 @@ constexpr Accel kAccels[] = {
|
||||
{GDK_KEY_space, 0, AppAction::PauseToggle, "space", "Pause or resume the display",
|
||||
"Acquisition"},
|
||||
{GDK_KEY_a, 0, AppAction::TriggerArm, "a", "Arm or disarm the trigger", "Acquisition"},
|
||||
{GDK_KEY_t, 0, AppAction::TriggerPanel, "t", "Show or hide the trigger panel",
|
||||
"Acquisition"},
|
||||
{GDK_KEY_c, 0, AppAction::CursorsPane, "c", "Show or hide the cursors pane",
|
||||
"Panes and editors"},
|
||||
{GDK_KEY_m, 0, AppAction::MeasuresPane, "m", "Show or hide the measurements pane",
|
||||
"Panes and editors"},
|
||||
{GDK_KEY_g, 0, AppAction::MathPane, "g", "Show or hide the math pane",
|
||||
"Panes and editors"},
|
||||
{GDK_KEY_t, 0, AppAction::TriggerPanel, "t", "Enable or disable trigger mode", "Acquisition"},
|
||||
{GDK_KEY_c, 0, AppAction::CursorsPane, "c", "Enable or disable the cursors", "Panes and editors"},
|
||||
{GDK_KEY_m, 0, AppAction::SideTabMeasure, "m", "Show the Measure tab", "Panes and editors"},
|
||||
{GDK_KEY_g, 0, AppAction::SideTabSignals, "g", "Show the Signals tab", "Panes and editors"},
|
||||
{GDK_KEY_l, 0, AppAction::LayoutEditor, "l", "Show or hide the layout editor",
|
||||
"Panes and editors"},
|
||||
{GDK_KEY_s, 0, AppAction::SidePanel, "s", "Show or hide the side panel",
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ enum class AppAction {
|
||||
TriggerArm,
|
||||
TriggerPanel,
|
||||
CursorsPane,
|
||||
MeasuresPane,
|
||||
MathPane,
|
||||
SideTabMeasure,
|
||||
SideTabSignals,
|
||||
LayoutEditor,
|
||||
SidePanel,
|
||||
Export,
|
||||
|
||||
+72
-104
@@ -2824,11 +2824,28 @@ void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer
|
||||
open_row_menu(*app, row, x, y, widget);
|
||||
}
|
||||
|
||||
void on_side_tab_changed(GObject* o, GParamSpec*, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
const char* name = adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(o));
|
||||
app->side_tab = name != nullptr ? name : "signals";
|
||||
// Not while the layout is being applied: the restore would schedule a save of what it just read.
|
||||
if (!app->updating_panels) schedule_layout_save(*app);
|
||||
}
|
||||
|
||||
void select_side_tab(App& app, const char* name) {
|
||||
if (app.side_stack == nullptr || name == nullptr) return;
|
||||
// An unknown name leaves the stack where it is -- a session file from a later version must not
|
||||
// be able to blank the sidebar.
|
||||
if (adw_view_stack_get_child_by_name(ADW_VIEW_STACK(app.side_stack), name) == nullptr) return;
|
||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(app.side_stack), name);
|
||||
app.side_tab = name;
|
||||
}
|
||||
|
||||
/// The side column is on when it has something to show *and* the user's sidebar
|
||||
/// button is in; the two panel buttons appear as soon as a CONFIG describes any
|
||||
/// signal at all.
|
||||
void update_side_visibility(App& app) {
|
||||
if (app.side_scroll == nullptr) return;
|
||||
if (app.side_stack == nullptr) return;
|
||||
const bool have_signals = !app.model->signals().empty();
|
||||
// `have_signals` decides whether the sidebar *can* be on; `sidebar_wanted` is the user's last word,
|
||||
// which is why it is kept apart from the button's own state -- that state follows the panel now, and
|
||||
@@ -2845,7 +2862,6 @@ void update_side_visibility(App& app) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.sidebar_toggle), want_column);
|
||||
app.sidebar_syncing = false;
|
||||
}
|
||||
gtk_widget_set_visible(app.signal_toggle, have_signals);
|
||||
gtk_widget_set_visible(app.trigger_toggle, have_signals);
|
||||
}
|
||||
|
||||
@@ -2932,15 +2948,15 @@ void note_panel_controls(App& app) {
|
||||
const auto in_cursors_pane = [&app](GtkWidget* w) {
|
||||
for (GtkWidget* p = w != nullptr ? gtk_widget_get_parent(w) : nullptr; p != nullptr;
|
||||
p = gtk_widget_get_parent(p)) {
|
||||
if (p == app.cursors_revealer) return 1;
|
||||
if (p == app.cursors_panel) return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
// `HeaderBar::pack_end()` parents into an internal box, so the bar is a level up, not the parent.
|
||||
char text[256];
|
||||
std::snprintf(text, sizeof(text),
|
||||
"cursors=%d measurements=%d math=%d in the toolbar, reset=%d in the cursors pane",
|
||||
in_bar(app.cursors_toggle), in_bar(app.measures_toggle), in_bar(app.math_toggle),
|
||||
"cursors=%d in the toolbar, switcher=%d, reset=%d in the cursors pane",
|
||||
in_bar(app.cursors_toggle), app.side_switcher != nullptr ? 1 : 0,
|
||||
in_cursors_pane(app.cursors_reset));
|
||||
// ... and the order the bar actually shows, which is what "the menu should be last" is about. A
|
||||
// widget tree walked in child order *is* the layout order, so this is the reading order of the
|
||||
@@ -2948,8 +2964,7 @@ void note_panel_controls(App& app) {
|
||||
const std::pair<GtkWidget*, const char*> named[] = {
|
||||
{app.trigger_toggle, "trigger"}, {app.events_toggle, "events"},
|
||||
{app.pause_button, "pause"},
|
||||
{app.cursors_toggle, "cursors"}, {app.measures_toggle, "measures"},
|
||||
{app.math_toggle, "math"}, {app.sidebar_toggle, "sidebar"},
|
||||
{app.cursors_toggle, "cursors"}, {app.sidebar_toggle, "sidebar"},
|
||||
{app.menu_button, "menu"},
|
||||
};
|
||||
// The widget tree's child order, which *is* the visual order left to right: a box lays its children
|
||||
@@ -4316,12 +4331,8 @@ void run_app_action(App& app, AppAction action) {
|
||||
case AppAction::CursorsPane:
|
||||
toggle(app.cursors_toggle);
|
||||
break;
|
||||
case AppAction::MeasuresPane:
|
||||
toggle(app.measures_toggle);
|
||||
break;
|
||||
case AppAction::MathPane:
|
||||
toggle(app.math_toggle);
|
||||
break;
|
||||
case AppAction::SideTabMeasure: select_side_tab(app, "measure"); break;
|
||||
case AppAction::SideTabSignals: select_side_tab(app, "signals"); break;
|
||||
case AppAction::LayoutEditor:
|
||||
toggle(app.layout_toggle);
|
||||
break;
|
||||
@@ -4449,11 +4460,6 @@ GtkWidget* build_toolbar(App& app) {
|
||||
g_signal_connect(app.layout_toggle, "toggled", G_CALLBACK(on_layout_editor_toggled), &app);
|
||||
gtk_box_append(GTK_BOX(menu_box), app.layout_toggle);
|
||||
|
||||
app.signal_toggle = menu_toggle("Signal list", nullptr, true);
|
||||
gtk_widget_set_tooltip_text(app.signal_toggle, "show or hide the signal list");
|
||||
g_signal_connect(app.signal_toggle, "toggled", G_CALLBACK(on_signals_toggled), &app);
|
||||
gtk_box_append(GTK_BOX(menu_box), app.signal_toggle);
|
||||
|
||||
gtk_box_append(GTK_BOX(menu_box), menu_separator());
|
||||
|
||||
GtkWidget* export_btn = menu_button("Export visible range...", "Ctrl+E");
|
||||
@@ -4511,12 +4517,10 @@ GtkWidget* build_toolbar(App& app) {
|
||||
app.trigger_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "trigger_toggle"));
|
||||
app.events_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "events_toggle"));
|
||||
app.pause_button = GTK_WIDGET(gtk_builder_get_object(ui, "pause_button"));
|
||||
app.measures_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "measures_toggle"));
|
||||
app.math_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "math_toggle"));
|
||||
app.cursors_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "cursors_toggle"));
|
||||
if (app.menu_button == nullptr || app.sidebar_toggle == nullptr || app.trigger_toggle == nullptr ||
|
||||
app.events_toggle == nullptr || app.pause_button == nullptr || app.measures_toggle == nullptr ||
|
||||
app.math_toggle == nullptr || app.cursors_toggle == nullptr) {
|
||||
app.events_toggle == nullptr || app.pause_button == nullptr ||
|
||||
app.cursors_toggle == nullptr) {
|
||||
// A missing id is a mistake in the `.ui`: the bar comes up with the controls it got rather than taking
|
||||
// the window with it, and `[udpscope] header:` reads whatever geometry is left.
|
||||
return bar;
|
||||
@@ -4538,24 +4542,6 @@ GtkWidget* build_toolbar(App& app) {
|
||||
}),
|
||||
&app);
|
||||
g_signal_connect(app.pause_button, "toggled", G_CALLBACK(on_pause_toggled), &app);
|
||||
g_signal_connect(app.measures_toggle, "toggled",
|
||||
G_CALLBACK(+[](GtkToggleButton* b, gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
a->measures_revealed = gtk_toggle_button_get_active(b);
|
||||
if (a->measures_revealer != nullptr) {
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(a->measures_revealer),
|
||||
a->measures_revealed);
|
||||
}
|
||||
}),
|
||||
&app);
|
||||
g_signal_connect(app.math_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* b, gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->math_revealer != nullptr) {
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(a->math_revealer),
|
||||
gtk_toggle_button_get_active(b));
|
||||
}
|
||||
}),
|
||||
&app);
|
||||
g_signal_connect(app.cursors_toggle, "toggled", G_CALLBACK(on_cursors_toggled), &app);
|
||||
return bar;
|
||||
}
|
||||
@@ -4579,14 +4565,13 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
app->status_badge = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_badge"));
|
||||
app->status_label = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_label"));
|
||||
app->status_info_button = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "status_info"));
|
||||
app->side_scroll = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_scroll"));
|
||||
GtkWidget* side = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_column"));
|
||||
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
||||
// be a null pointer.
|
||||
if (app->window == nullptr || side_view == nullptr || events_divider == nullptr ||
|
||||
app->plot_box == nullptr || app->plot_split == nullptr || app->status_badge == nullptr ||
|
||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||
app->side_scroll == nullptr || side == nullptr) {
|
||||
side == nullptr) {
|
||||
return;
|
||||
}
|
||||
// The window's actions live on the application, so it has to know which one it belongs to.
|
||||
@@ -4714,59 +4699,46 @@ void on_activate(GtkApplication* application, gpointer user) {
|
||||
gtk_popover_set_has_arrow(GTK_POPOVER(app->status_popover), TRUE);
|
||||
}
|
||||
|
||||
// The right-hand side: two independently collapsible panels, each revealed by
|
||||
// its toolbar toggle. No outer scrolled window -- the signal list scrolls
|
||||
// inside its own panel -- so neither panel can be squeezed to nothing by the
|
||||
// other's size request.
|
||||
// `side` is the sidebar column from the `.ui`, resolved above with the rest of the skeleton; the
|
||||
// app keeps it because the panel geometry, the divide and the revealers all ask about it.
|
||||
// The right-hand side is two tabs, not five stacked panels: one tab is on screen at a time, so
|
||||
// no section competes with four others for height. Each page scrolls on its own -- the two are
|
||||
// different heights, and one shared scroller would leave a switch scrolled where the new page
|
||||
// never asked to be.
|
||||
// `side` is `side_column` from the `.ui`, resolved above with the rest of the skeleton; the app
|
||||
// keeps it because `update_side_visibility()` still asks about the column as a whole.
|
||||
app->side_box = side;
|
||||
app->side_stack = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_stack"));
|
||||
app->side_switcher = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "side_switcher"));
|
||||
GtkWidget* measure_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "measure_page"));
|
||||
GtkWidget* signals_page = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "signals_page"));
|
||||
|
||||
// The two panels share a vertical box, so they collapse *vertically*: a
|
||||
// horizontal slide would animate the width and leave the panel's full height
|
||||
// reserved -- an invisible gap pushing the other panel down the column.
|
||||
app->trigger_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(app->trigger_revealer),
|
||||
GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
// Measure: what the cursors bracket, and the figures derived from it.
|
||||
app->cursors_panel = build_cursors_panel(*app);
|
||||
gtk_box_append(GTK_BOX(measure_page), app->cursors_panel);
|
||||
gtk_box_append(GTK_BOX(measure_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
gtk_box_append(GTK_BOX(measure_page), build_measures_panel(*app));
|
||||
|
||||
// Signals: the trigger picks a channel from the list beside it.
|
||||
app->trigger_panel = build_trigger_panel(*app);
|
||||
gtk_revealer_set_child(GTK_REVEALER(app->trigger_revealer), app->trigger_panel);
|
||||
gtk_box_append(GTK_BOX(side), app->trigger_revealer);
|
||||
gtk_box_append(GTK_BOX(side), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
|
||||
// The cursors readout sits with the other panels and is revealed by its toggle: the
|
||||
// instruments are on the plot, but what they read has to be somewhere a value can be
|
||||
// selected and copied from.
|
||||
app->cursors_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(app->cursors_revealer),
|
||||
GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
gtk_revealer_set_child(GTK_REVEALER(app->cursors_revealer), build_cursors_panel(*app));
|
||||
gtk_box_append(GTK_BOX(side), app->cursors_revealer);
|
||||
gtk_box_append(GTK_BOX(side), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
|
||||
app->measures_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(app->measures_revealer),
|
||||
GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
gtk_revealer_set_child(GTK_REVEALER(app->measures_revealer), build_measures_panel(*app));
|
||||
gtk_box_append(GTK_BOX(side), app->measures_revealer);
|
||||
gtk_box_append(GTK_BOX(side), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
|
||||
app->math_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(app->math_revealer),
|
||||
GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
gtk_revealer_set_child(GTK_REVEALER(app->math_revealer), build_math_panel(*app));
|
||||
gtk_box_append(GTK_BOX(side), app->math_revealer);
|
||||
gtk_box_append(GTK_BOX(side), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
gtk_box_append(GTK_BOX(signals_page), app->trigger_panel);
|
||||
gtk_box_append(GTK_BOX(signals_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
// Still on the signals page until the list grows the `+` and the per-row menu that replace it.
|
||||
gtk_box_append(GTK_BOX(signals_page), build_math_panel(*app));
|
||||
gtk_box_append(GTK_BOX(signals_page), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||
rebuild_math_pane(*app);
|
||||
|
||||
app->signal_panel = build_signal_panel(*app);
|
||||
app->signal_revealer = gtk_revealer_new();
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(app->signal_revealer),
|
||||
GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
gtk_revealer_set_child(GTK_REVEALER(app->signal_revealer), app->signal_panel);
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(app->signal_revealer), TRUE);
|
||||
gtk_box_append(GTK_BOX(side), app->signal_revealer);
|
||||
// Its size request and the scroller around it are the `.ui`'s: the column scrolls as a whole, so a
|
||||
// long signal list or a revealed trigger panel can never push either panel out of the window.
|
||||
gtk_box_append(GTK_BOX(signals_page), app->signal_panel);
|
||||
|
||||
// Both instruments start off, so both panels start insensitive -- stated here by the one place
|
||||
// that knows the default, rather than left to whichever widget happened to be built first.
|
||||
gtk_widget_set_sensitive(app->trigger_panel, FALSE);
|
||||
gtk_widget_set_sensitive(app->cursors_panel, FALSE);
|
||||
gtk_widget_set_sensitive(app->cursors_reset, FALSE);
|
||||
|
||||
if (app->side_stack != nullptr) {
|
||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(app->side_stack), "signals");
|
||||
g_signal_connect(app->side_stack, "notify::visible-child-name",
|
||||
G_CALLBACK(on_side_tab_changed), app);
|
||||
}
|
||||
|
||||
// The left side is the events pane *and* the plot, in one row: the pane is a column of its own
|
||||
// (the item's "collapsable pane on the left"), revealed by trigger mode.
|
||||
@@ -5494,9 +5466,6 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_WINDOWPOP_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), windowpop_probe_step, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_KEYS_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), keys_probe, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_COLOR_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), color_probe, &app);
|
||||
}
|
||||
@@ -5604,7 +5573,7 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
std::printf("[udpscope] axes.w changes: %d (last at frame %d, side panel up=%d)\n",
|
||||
app.dbg_axes_w_changes, app.dbg_axes_w_change_frame,
|
||||
app.dbg_axes_w_change_side);
|
||||
std::printf("[udpscope] reset button visible: %d\n", app.dbg_reset_visible);
|
||||
std::printf("[udpscope] reset button sensitive: %d\n", app.dbg_reset_sensitive);
|
||||
std::printf("[udpscope] render blank frames: %d of %d painted\n",
|
||||
app.dbg_render_blank_frames, app.dbg_frames_painted);
|
||||
std::printf("[udpscope] traceless paints: %d (%d after the first bind, last at paint %d)\n",
|
||||
@@ -5671,12 +5640,12 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
inside(app.events_list, app.events_revealer));
|
||||
}
|
||||
std::printf("[udpscope] panels: side=%dx%d (visible=%d) signals=%dx%d row_h=%d "
|
||||
"(revealed=%d) trigger=%dx%d (revealed=%d) toggles=%d cursors=%d window=%.3fs "
|
||||
"(page_current=%d) trigger=%dx%d (sensitive=%d) switcher=%d cursors=%d window=%.3fs "
|
||||
"window_cap=%.1fs\n",
|
||||
app.dbg_side_w, app.dbg_side_h, app.dbg_side_visible, app.dbg_signal_w,
|
||||
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_revealed,
|
||||
app.dbg_signal_h, app.dbg_signal_row_h, app.dbg_signal_page_current,
|
||||
app.dbg_trigger_w, app.dbg_trigger_h,
|
||||
app.dbg_trigger_revealed, app.dbg_toggles_visible,
|
||||
app.dbg_trigger_sensitive, app.dbg_switcher_visible,
|
||||
rows.cursors_visible() ? 1 : 0, rows.time_window(),
|
||||
app.window_limit_s);
|
||||
std::printf("[udpscope] mode with trigger on: %s\n", app.dbg_mode_triggered.c_str());
|
||||
@@ -5841,11 +5810,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
std::printf("[udpscope] badge frames: lit=%d dim=%d idle=%d connecting=%d\n",
|
||||
app.dbg_badge_frames[0], app.dbg_badge_frames[1], app.dbg_badge_frames[2],
|
||||
app.dbg_badge_frames[3]);
|
||||
std::printf("[udpscope] trigger panel revealed before the probe toggled it: %d; "
|
||||
"signal list items=%d selected=%d; column visible after sidebar "
|
||||
"toggle off=%d\n",
|
||||
app.dbg_trigger_revealed_default, app.dbg_trigger_items,
|
||||
app.dbg_trigger_selected, app.dbg_side_scroll_after_hide);
|
||||
std::printf("[udpscope] trigger panel sensitive before the probe toggled it: %d; "
|
||||
"signal list items=%d selected=%d; column visible after sidebar toggle off=%d\n",
|
||||
app.dbg_trigger_sensitive_default, app.dbg_trigger_items,
|
||||
app.dbg_trigger_selected, app.dbg_side_column_after_hide);
|
||||
{
|
||||
// The panel's *frame* read back: the row boxes the `.ui` holds, and that every control the code built
|
||||
// ended up inside the panel rather than in a row that is not there. A control is created in code and
|
||||
@@ -6078,9 +6046,9 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(model.mutex());
|
||||
std::printf("[udpscope] measures pane: n=%zu revealed=%d marks_shown=%d pane_rows=%zu "
|
||||
std::printf("[udpscope] measures pane: n=%zu marks_shown=%d pane_rows=%zu "
|
||||
"pane_w=%d pane_h=%d switch_on=%d reset_sensitive=%d\n",
|
||||
app.measures.size(), app.measures_revealed ? 1 : 0,
|
||||
app.measures.size(),
|
||||
rows.measure_marks_placed(), app.measure_labels.size(),
|
||||
app.dbg_measures_pane_w, app.dbg_measures_pane_h,
|
||||
// The two widgets the panel's *frame* is read by: the code drives `measure_stats_on`,
|
||||
|
||||
+2
-14
@@ -3,7 +3,7 @@
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The header bar: the time-window control's slot, the trigger, pause and events group with the separator that
|
||||
marks where the *view's* controls end, and the five at the far end.
|
||||
marks where the *view's* controls end, and the three at the far end.
|
||||
|
||||
The bar carries only the controls you reach for *while watching a signal*; everything you set up once lives
|
||||
in the overflow menu, which is code — its rows act on the `RowSet` directly and two of them are toggles
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
The order is the file's, and it is the one thing here that is not self-evident: `pack_end()` lays its
|
||||
children out right to left, so a `<child type="end">` is added leftwards-from-the-right in document order
|
||||
and the screen reads the reverse of it — cursors, measurements, math, sidebar, menu.
|
||||
and the screen reads the reverse of it — cursors, sidebar, menu.
|
||||
|
||||
The first start child is a slot and not the control: the time-window control is code (its menu's rows come
|
||||
from the buffer's limits), and `gtk_header_bar_pack_start()` appends *after* whatever a file has already
|
||||
@@ -64,18 +64,6 @@
|
||||
<property name="active">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkToggleButton" id="math_toggle">
|
||||
<property name="icon-name">udpscope-math-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">channels derived from the others by a graph of operations: gain, absolute value, filters, sums and the rest. An enabled one is acquired, plotted and measured exactly like a signal</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkToggleButton" id="measures_toggle">
|
||||
<property name="icon-name">udpscope-measure-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">compute a value over the visible window of a signal, and optionally mark it on the plot</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkToggleButton" id="cursors_toggle">
|
||||
<property name="icon-name">udpscope-cursors-symbolic</property>
|
||||
|
||||
+54
-9
@@ -3,7 +3,7 @@
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
||||
events pane's divider, the plot column with the status line under it, and the sidebar's scroller.
|
||||
events pane's divider, the plot column with the status line under it, and the sidebar's tab stack.
|
||||
|
||||
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
|
||||
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
|
||||
@@ -20,7 +20,7 @@
|
||||
What is *not* here is as deliberate as what is. Everything this file nests is fixed — the shape of
|
||||
the window does not depend on the stream, the trigger or the layout — while the subtrees that do are
|
||||
built by code and put in place: the events pane, into `events_divider`'s start side; the two plot
|
||||
areas, into the split inside `plot_box`; the sidebar's panels, appended to `side_column`; and the
|
||||
areas, into the split inside `plot_box`; the sidebar's panels, appended to `measure_page` and `signals_page`; and the
|
||||
status popover, attached to `status_info`.
|
||||
|
||||
The header bar is not here either: it is the window's own titlebar, built by `build_toolbar()`
|
||||
@@ -48,20 +48,65 @@
|
||||
<property name="min-sidebar-width">320</property>
|
||||
<property name="max-sidebar-width">320</property>
|
||||
<property name="sidebar">
|
||||
<!-- The column scrolls as a whole, so a long signal list or a revealed trigger panel can
|
||||
never push either panel out of the window. -->
|
||||
<object class="GtkScrolledWindow" id="side_scroll">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="vscrollbar-policy">automatic</property>
|
||||
<property name="min-content-width">300</property>
|
||||
<property name="child">
|
||||
<!-- A switcher above a stack, and a scroller *per page*: the two pages are different
|
||||
heights, so one shared scroller around the stack would leave a tab switch scrolled
|
||||
to somewhere the new page never asked for. The switcher is the sibling above, not a
|
||||
child of either scroller: the one control that chooses what you are looking at must
|
||||
not be the first thing to scroll away. -->
|
||||
<object class="GtkBox" id="side_column">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="width-request">300</property>
|
||||
<child>
|
||||
<object class="AdwViewSwitcher" id="side_switcher">
|
||||
<property name="stack">side_stack</property>
|
||||
<property name="policy">wide</property>
|
||||
<property name="margin-top">4</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="margin-start">6</property>
|
||||
<property name="margin-end">6</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwViewStack" id="side_stack">
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="AdwViewStackPage">
|
||||
<property name="name">measure</property>
|
||||
<property name="title" translatable="yes">Measure</property>
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="vscrollbar-policy">automatic</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox" id="measure_page">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwViewStackPage">
|
||||
<property name="name">signals</property>
|
||||
<property name="title" translatable="yes">Signals</property>
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="vscrollbar-policy">automatic</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox" id="signals_page">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="content">
|
||||
<object class="GtkPaned" id="events_divider">
|
||||
<property name="orientation">horizontal</property>
|
||||
|
||||
+19
-21
@@ -139,18 +139,21 @@ struct App {
|
||||
/// what we synced is ours -- GTK only notifies on a change, so the user cannot produce it.
|
||||
int window_sync_unit = -1;
|
||||
GtkWidget* trigger_toggle = nullptr;
|
||||
GtkWidget* trigger_revealer = nullptr;
|
||||
/// The trigger panel's own box, kept so the defaults probe can read its spin buttons without
|
||||
/// walking the window for them.
|
||||
GtkWidget* trigger_panel = nullptr;
|
||||
GtkWidget* signal_toggle = nullptr;
|
||||
GtkWidget* signal_revealer = nullptr;
|
||||
GtkWidget* signal_panel = nullptr;
|
||||
GtkWidget* side_box = nullptr;
|
||||
/// The sidebar's tab stack and its switcher. The stack is what chooses which panels are on
|
||||
/// screen; a feature's own on/off never does (it sets its panel's sensitivity instead).
|
||||
GtkWidget* side_stack = nullptr;
|
||||
GtkWidget* side_switcher = nullptr;
|
||||
/// The page name the stack is showing, and the one value the session file persists about the
|
||||
/// sidebar. "signals" on a first run.
|
||||
std::string side_tab = "signals";
|
||||
GtkWidget* arm_button = nullptr;
|
||||
GtkWidget* trigger_signal_drop = nullptr;
|
||||
GtkWidget* sidebar_toggle = nullptr;
|
||||
GtkWidget* side_scroll = nullptr;
|
||||
GtkWidget* trigger_edge_drop = nullptr;
|
||||
GtkWidget* trigger_type_drop = nullptr;
|
||||
GtkWidget* trigger_cmp_drop = nullptr;
|
||||
@@ -211,10 +214,10 @@ struct App {
|
||||
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
||||
GtkWidget* toolbar = nullptr;
|
||||
GtkWidget* cursors_reset = nullptr;
|
||||
/// The cursors pane: revealed with the toggle, and listing what the instruments
|
||||
/// read -- T0/T1/dT always, per row an Y1/Y2/dY where rulers are up, and in `mix` mode
|
||||
/// the value each cursor crosses each trace at.
|
||||
GtkWidget* cursors_revealer = nullptr;
|
||||
/// The cursors panel's root, kept because cursors-off disables it rather than hiding it.
|
||||
GtkWidget* cursors_panel = nullptr;
|
||||
/// The cursors pane: listing what the instruments read -- T0/T1/dT always, per row an Y1/Y2/dY
|
||||
/// where rulers are up, and in `mix` mode the value each cursor crosses each trace at.
|
||||
GtkWidget* cursors_mode_drop = nullptr;
|
||||
GtkWidget* cursors_body = nullptr;
|
||||
/// One line per row (rulers) and one per bound trace (`mix`), in the order the body
|
||||
@@ -237,16 +240,12 @@ struct App {
|
||||
/// price the frame tick should pay).
|
||||
std::vector<Measure> measures;
|
||||
std::vector<MeasureResult> measure_results;
|
||||
GtkWidget* measures_revealer = nullptr;
|
||||
GtkWidget* measures_list = nullptr;
|
||||
GtkWidget* measures_toggle = nullptr;
|
||||
// --- math channels --------------------------------------------------------
|
||||
/// The app's copy of the math channels, and the source of truth for the pane. Every change goes
|
||||
/// through `apply_math_channels()`, which writes this and hands it to the model -- the model's own
|
||||
/// copy is what the producer evaluates.
|
||||
std::vector<MathChannel> math;
|
||||
GtkWidget* math_toggle = nullptr;
|
||||
GtkWidget* math_revealer = nullptr;
|
||||
GtkWidget* math_list = nullptr;
|
||||
/// The math editor's builder. The editor's window belongs to the builder's object list, so the builder is
|
||||
/// held for as long as the window is up and cleared on the *next* opening rather than with the window --
|
||||
@@ -358,7 +357,6 @@ struct App {
|
||||
/// that memory reused and the read is a **SIGSEGV** in the middle of the dump.
|
||||
int dbg_measures_pane_w = -1;
|
||||
int dbg_measures_pane_h = -1;
|
||||
bool measures_revealed = false;
|
||||
/// The measurement dialog while one is open (null otherwise). A probe needs a handle on it:
|
||||
/// it is a window of its own, so the main window's widget tree does not reach its buttons.
|
||||
GtkWidget* measure_dialog = nullptr;
|
||||
@@ -508,7 +506,6 @@ struct App {
|
||||
/// True once the renderer's `Style` has been stated (see `tick()`): the renderer does not
|
||||
/// exist until the area is realized, so the one-shot has to be a tick's job.
|
||||
bool style_stated = false;
|
||||
bool signals_panel_initialised = false;
|
||||
/// Set once the user edits the trigger level, so the panel stops defaulting it
|
||||
/// into the middle of the declared range.
|
||||
bool trigger_level_touched = false;
|
||||
@@ -690,7 +687,7 @@ struct App {
|
||||
/// over a run that acquired anything -- that is what "it blinks" means, and a badge
|
||||
/// stuck lit looks identical to a working one in a screenshot.
|
||||
int dbg_badge_frames[4] = {0, 0, 0, 0};
|
||||
int dbg_reset_visible = -1;
|
||||
int dbg_reset_sensitive = -1;
|
||||
std::vector<std::uint64_t> last_stored;
|
||||
std::uint64_t dbg_latches_at_disarm = 0;
|
||||
std::uint64_t dbg_seq_at_disarm = 0;
|
||||
@@ -738,12 +735,12 @@ struct App {
|
||||
int dbg_signal_row_h = 0;
|
||||
int dbg_trigger_w = 0;
|
||||
int dbg_trigger_h = 0;
|
||||
int dbg_signal_revealed = 0;
|
||||
int dbg_trigger_revealed = 0;
|
||||
int dbg_signal_page_current = 0;
|
||||
int dbg_trigger_sensitive = 0;
|
||||
int dbg_side_visible = 0;
|
||||
int dbg_trigger_items = 0;
|
||||
int dbg_trigger_selected = -1;
|
||||
int dbg_side_scroll_after_hide = 1;
|
||||
int dbg_side_column_after_hide = 1;
|
||||
std::string dbg_mode_triggered;
|
||||
std::string dbg_window_text;
|
||||
double dbg_marker_x = 0.0;
|
||||
@@ -751,8 +748,8 @@ struct App {
|
||||
double dbg_trigger_time = 0.0;
|
||||
int dbg_trigger_time_valid = -1;
|
||||
std::string dbg_mode_live;
|
||||
int dbg_toggles_visible = 0;
|
||||
int dbg_trigger_revealed_default = 0;
|
||||
int dbg_switcher_visible = 0;
|
||||
int dbg_trigger_sensitive_default = 0;
|
||||
|
||||
/// The detail string the popover and the exit dump read. The dump used to read the label,
|
||||
/// which no longer carries these fields.
|
||||
@@ -1336,7 +1333,6 @@ void rebuild_math_pane(App& app);
|
||||
GtkWidget* build_measures_panel(App& app);
|
||||
GtkWidget* build_cursors_panel(App& app);
|
||||
void on_cursors_toggled(GtkToggleButton* button, gpointer user);
|
||||
void on_signals_toggled(GtkToggleButton* button, gpointer user);
|
||||
void on_fit_clicked(GtkButton*, gpointer user);
|
||||
void on_fit_x_clicked(GtkButton*, gpointer user);
|
||||
void on_cursors_reset(GtkButton*, gpointer user);
|
||||
@@ -1359,6 +1355,8 @@ void row_menu_fill(RowMenuState& st);
|
||||
GtkWidget* open_row_menu(App& app, int row, double x, double y, GtkWidget* parent, bool autohide = true);
|
||||
void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer user);
|
||||
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y, gpointer user);
|
||||
void on_side_tab_changed(GObject*, GParamSpec*, gpointer user);
|
||||
void select_side_tab(App& app, const char* name);
|
||||
void update_side_visibility(App& app);
|
||||
void on_sidebar_toggled(GtkToggleButton* button, gpointer user);
|
||||
void rebuild_signal_panels(App& app);
|
||||
|
||||
+17
-8
@@ -14,7 +14,18 @@ namespace ui_impl {
|
||||
|
||||
/// `%.6g s` is exact enough for a cursor readout and short enough for the panel.
|
||||
void update_cursors_pane(App& app) {
|
||||
if (app.cursors_body == nullptr || !app.rows->cursors_visible()) return;
|
||||
if (app.cursors_body == nullptr) return;
|
||||
if (!app.rows->cursors_visible()) {
|
||||
// A readout quotes a stored sample; with the instruments off there is no sample it quotes, so
|
||||
// the cells blank rather than keeping the last ones. `"--"` is the same spelling an
|
||||
// unavailable cell already uses -- two spellings of one state in one grid would be the defect.
|
||||
for (const App::CursorLine& line : app.cursors_lines) {
|
||||
if (line.a != nullptr) gtk_label_set_text(GTK_LABEL(line.a), "--");
|
||||
if (line.b != nullptr) gtk_label_set_text(GTK_LABEL(line.b), "--");
|
||||
if (line.d != nullptr) gtk_label_set_text(GTK_LABEL(line.d), "--");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (app.cursors_pane_mode != static_cast<int>(app.rows->cursor_mode()) ||
|
||||
app.cursors_pane_rows != app.rows->row_count() ||
|
||||
app.cursors_pane_generation != app.model->generation() ||
|
||||
@@ -117,13 +128,11 @@ void on_cursors_toggled(GtkToggleButton* button, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
const bool on = gtk_toggle_button_get_active(button);
|
||||
app->rows->set_cursors_visible(on);
|
||||
// The reset button exists for the cursors: with them hidden there is nothing to
|
||||
// reset, and a button that does nothing reads as broken.
|
||||
gtk_widget_set_visible(app->cursors_reset, on);
|
||||
// The pane is the cursors' readout: with them off there is nothing to read.
|
||||
if (app->cursors_revealer != nullptr) {
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(app->cursors_revealer), on);
|
||||
}
|
||||
// Disabled, not hidden, and the same for the reset beside it: with the cursors off there is
|
||||
// nothing to reset, but the button must stay where the user will look for it.
|
||||
gtk_widget_set_sensitive(app->cursors_reset, on);
|
||||
if (app->cursors_panel != nullptr) gtk_widget_set_sensitive(app->cursors_panel, on);
|
||||
if (!on) update_cursors_pane(*app);
|
||||
if (on) {
|
||||
app->rows->reset_cursors();
|
||||
rebuild_cursors_pane(*app);
|
||||
|
||||
+3
-3
@@ -18,11 +18,11 @@ void apply_math_channels(App& app) {
|
||||
// `set_math_channels()` takes the model's lock itself -- holding it here is a self-deadlock on a
|
||||
// non-recursive mutex, and it is exactly how the first version of the probe hung the app.
|
||||
app.model->set_math_channels(app.math);
|
||||
// The structural rebuild above replaced the buffer and dropped its history. A stream's producer
|
||||
// refills that; a file's nothing does, so the view is re-read -- and with the math set now in place
|
||||
// the reload computes the derived channels over the file as it goes.
|
||||
reload_file_view(app);
|
||||
rebuild_math_pane(app);
|
||||
// The math channels are rows of the signal list too -- `append_math_signals_locked()` puts them
|
||||
// in `signals()` -- so the list is stale until it is rebuilt.
|
||||
rebuild_signal_panels(app);
|
||||
schedule_layout_save(app);
|
||||
}
|
||||
|
||||
|
||||
+45
-43
@@ -74,24 +74,36 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
a->window = nullptr; // stop the tick before the widgets go
|
||||
a->dbg_side_w = gtk_widget_get_width(a->side_box);
|
||||
a->dbg_side_h = gtk_widget_get_height(a->side_box);
|
||||
a->dbg_signal_w = gtk_widget_get_width(a->signal_revealer);
|
||||
a->dbg_signal_h = gtk_widget_get_height(a->signal_revealer);
|
||||
a->dbg_trigger_w = gtk_widget_get_width(a->trigger_revealer);
|
||||
a->dbg_trigger_h = gtk_widget_get_height(a->trigger_revealer);
|
||||
a->dbg_signal_w = gtk_widget_get_width(a->signal_panel);
|
||||
a->dbg_signal_h = gtk_widget_get_height(a->signal_panel);
|
||||
a->dbg_trigger_w = gtk_widget_get_width(a->trigger_panel);
|
||||
a->dbg_trigger_h = gtk_widget_get_height(a->trigger_panel);
|
||||
// The height of one signal row, which is the thing "the channel toggle is too tall"
|
||||
// was about. The revealer's own height cannot stand in for it: it also covers the
|
||||
// was about. The panel's own height cannot stand in for it: it also covers the
|
||||
// heading, the hint and the box margins.
|
||||
if (GtkWidget* first = gtk_widget_get_first_child(a->signal_list)) {
|
||||
a->dbg_signal_row_h = gtk_widget_get_height(first);
|
||||
}
|
||||
if (a->area != nullptr) a->dbg_frames_painted = static_cast<int>(a->area->frames_painted());
|
||||
a->dbg_reset_visible = a->cursors_reset != nullptr && gtk_widget_get_visible(a->cursors_reset) ? 1 : 0;
|
||||
a->dbg_signal_revealed =
|
||||
gtk_revealer_get_child_revealed(GTK_REVEALER(a->signal_revealer)) ? 1 : 0;
|
||||
a->dbg_trigger_revealed =
|
||||
gtk_revealer_get_child_revealed(GTK_REVEALER(a->trigger_revealer)) ? 1 : 0;
|
||||
a->dbg_side_visible = gtk_widget_get_visible(a->side_box) ? 1 : 0;
|
||||
a->dbg_side_scroll_after_hide = gtk_widget_get_visible(a->side_scroll) ? 1 : 0;
|
||||
a->dbg_reset_sensitive = a->cursors_reset != nullptr && gtk_widget_get_sensitive(a->cursors_reset) ? 1 : 0;
|
||||
// A page, not a revealer: a stack's hidden page measures 0x0 the way an unrevealed revealer
|
||||
// did, so the reading asks about *state* -- is this page the current one -- and never about size.
|
||||
const char* page = a->side_stack != nullptr
|
||||
? adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(a->side_stack))
|
||||
: nullptr;
|
||||
const int on_signals = page != nullptr && std::strcmp(page, "signals") == 0 ? 1 : 0;
|
||||
a->dbg_signal_page_current =
|
||||
on_signals && gtk_widget_get_first_child(a->signal_list) != nullptr ? 1 : 0;
|
||||
a->dbg_trigger_sensitive = on_signals && gtk_widget_get_sensitive(a->trigger_panel) ? 1 : 0;
|
||||
// `gtk_widget_get_visible` on the sidebar's GtkBox is always 1: AdwOverlaySplitView hides its
|
||||
// sidebar child via `gtk_widget_set_child_visible()`, not `gtk_widget_set_visible()`. The useful
|
||||
// reading is whether the split view is currently showing the sidebar panel.
|
||||
const int sidebar_shown = a->side_view != nullptr &&
|
||||
adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(a->side_view)) ? 1 : 0;
|
||||
a->dbg_side_column_after_hide = sidebar_shown;
|
||||
a->dbg_switcher_visible =
|
||||
a->side_switcher != nullptr && gtk_widget_get_visible(a->side_switcher) ? 1 : 0;
|
||||
a->dbg_side_visible = sidebar_shown;
|
||||
if (a->trigger_signal_drop != nullptr) {
|
||||
GListModel* model = gtk_drop_down_get_model(GTK_DROP_DOWN(a->trigger_signal_drop));
|
||||
a->dbg_trigger_items = model != nullptr ? static_cast<int>(g_list_model_get_n_items(model)) : 0;
|
||||
@@ -100,9 +112,6 @@ gboolean exit_probe_step3(gpointer user) {
|
||||
}
|
||||
// `dbg_window_text` is kept by `sync_window_display()`, the only place that knows the span the
|
||||
// window control is showing: there is no label to read it back from any more.
|
||||
a->dbg_toggles_visible =
|
||||
(gtk_widget_get_visible(a->signal_toggle) && gtk_widget_get_visible(a->trigger_toggle)) ? 1
|
||||
: 0;
|
||||
if (window != nullptr) gtk_window_destroy(GTK_WINDOW(window));
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
@@ -2186,8 +2195,7 @@ gboolean export_probe(gpointer user) {
|
||||
gboolean exit_probe_step1(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
// Record the panel states before the toggles are exercised.
|
||||
a->dbg_trigger_revealed_default =
|
||||
gtk_revealer_get_child_revealed(GTK_REVEALER(a->trigger_revealer)) ? 1 : 0;
|
||||
a->dbg_trigger_sensitive_default = gtk_widget_get_sensitive(a->trigger_panel) ? 1 : 0;
|
||||
if (a->trigger_toggle != nullptr) {
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->trigger_toggle), TRUE);
|
||||
}
|
||||
@@ -2227,13 +2235,10 @@ void cursors_probe_report(App& a, const char* what) {
|
||||
double my = 0.0;
|
||||
const bool have_mark = a.rows->crossing_at(0, 0, false, mx, my);
|
||||
std::fprintf(stderr,
|
||||
"[cursors-probe] %-14s mode=%-10s on=%d pane=%d t0=%.6f t1=%.6f dt=%.6f "
|
||||
"[cursors-probe] %-14s mode=%-10s on=%d pane_sensitive=%d t0=%.6f t1=%.6f dt=%.6f "
|
||||
"T=[%s] lines=%d marks=%d [%s]\n",
|
||||
what, kModes[mode], a.rows->cursors_visible() ? 1 : 0,
|
||||
a.cursors_revealer != nullptr &&
|
||||
gtk_revealer_get_reveal_child(GTK_REVEALER(a.cursors_revealer))
|
||||
? 1
|
||||
: 0,
|
||||
a.cursors_panel != nullptr && gtk_widget_get_sensitive(a.cursors_panel) ? 1 : 0,
|
||||
a.rows->cursor_a_x(), a.rows->cursor_b_x(),
|
||||
a.rows->cursor_b_x() - a.rows->cursor_a_x(), t_text.c_str(), n,
|
||||
a.rows->crossings_visible(), lines.c_str());
|
||||
@@ -2498,7 +2503,7 @@ gboolean measures_probe_step(gpointer user) {
|
||||
m.options.show_on_plot = measure_can_show_on_plot(m.type);
|
||||
a->measures.push_back(std::move(m));
|
||||
}
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->measures_toggle), true);
|
||||
select_side_tab(*a, "measure");
|
||||
rebuild_measures_pane(*a);
|
||||
std::fprintf(stderr, "[measures-probe] added %zu measurements on '%s'\n", a->measures.size(),
|
||||
signal.c_str());
|
||||
@@ -4027,29 +4032,26 @@ gboolean keys_probe(gpointer user) {
|
||||
|
||||
before_after("t: trigger panel", a->trigger_toggle, AppAction::TriggerPanel);
|
||||
before_after("c: cursors", a->cursors_toggle, AppAction::CursorsPane);
|
||||
before_after("m: measures", a->measures_toggle, AppAction::MeasuresPane);
|
||||
before_after("g: math", a->math_toggle, AppAction::MathPane);
|
||||
{
|
||||
const std::string before_tab = a->side_tab;
|
||||
run_app_action(*a, AppAction::SideTabMeasure);
|
||||
const std::string after_tab = a->side_tab;
|
||||
char buf[96];
|
||||
std::snprintf(buf, sizeof(buf), " m: tab %s->%s |", before_tab.c_str(), after_tab.c_str());
|
||||
line += buf;
|
||||
}
|
||||
{
|
||||
const std::string before_tab = a->side_tab;
|
||||
run_app_action(*a, AppAction::SideTabSignals);
|
||||
const std::string after_tab = a->side_tab;
|
||||
char buf[96];
|
||||
std::snprintf(buf, sizeof(buf), " g: tab %s->%s |", before_tab.c_str(), after_tab.c_str());
|
||||
line += buf;
|
||||
}
|
||||
before_after("l: layout", a->layout_toggle, AppAction::LayoutEditor);
|
||||
before_after("s: side panel", a->sidebar_toggle, AppAction::SidePanel);
|
||||
|
||||
// An accel must be refused while text is being typed. Focus the window entry, send 'm'
|
||||
// through the real handler, and require the measurements toggle not to move.
|
||||
int refused = -1;
|
||||
if (a->window_entry != nullptr) {
|
||||
gtk_menu_button_popup(GTK_MENU_BUTTON(a->window_button));
|
||||
gtk_widget_grab_focus(a->window_entry);
|
||||
const int before = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(a->measures_toggle))
|
||||
? 1 : 0;
|
||||
const gboolean handled = on_app_key_pressed(nullptr, GDK_KEY_m, 0,
|
||||
static_cast<GdkModifierType>(0), a);
|
||||
const int after = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(a->measures_toggle))
|
||||
? 1 : 0;
|
||||
refused = (handled == FALSE && before == after) ? 1 : 0;
|
||||
gtk_menu_button_popdown(GTK_MENU_BUTTON(a->window_button));
|
||||
}
|
||||
char tail[64];
|
||||
std::snprintf(tail, sizeof(tail), " editable focused: 'm' refused=%d\n", refused);
|
||||
line += tail;
|
||||
line += "\n";
|
||||
|
||||
std::fprintf(stderr, "%s", line.c_str());
|
||||
std::fflush(stderr);
|
||||
|
||||
+2
-15
@@ -137,6 +137,7 @@ void on_signal_color_chosen(GObject* source, GAsyncResult* res, gpointer user) {
|
||||
/// buttons made every row about twice this tall, which is what turned a four-signal
|
||||
/// stream into a panel you have to scroll.
|
||||
void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
if (app.signal_list == nullptr) return;
|
||||
app.updating_panels = true;
|
||||
GtkWidget* list = app.signal_list;
|
||||
GtkWidget* child = gtk_widget_get_first_child(list);
|
||||
@@ -300,12 +301,6 @@ void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals) {
|
||||
app.updating_panels = false;
|
||||
}
|
||||
|
||||
void on_signals_toggled(GtkToggleButton* button, gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(app->signal_revealer),
|
||||
gtk_toggle_button_get_active(button));
|
||||
}
|
||||
|
||||
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y,
|
||||
gpointer user) {
|
||||
App* app = static_cast<App*>(user);
|
||||
@@ -396,17 +391,9 @@ void rebuild_signal_panels(App& app) {
|
||||
rebuild_trigger_signals(app);
|
||||
refresh_window_limits(app);
|
||||
|
||||
// Nothing to configure before a CONFIG arrives: hide the whole column and its
|
||||
// toolbar buttons rather than showing two empty panels. The first CONFIG
|
||||
// reveals the signal panel (the trigger panel stays behind its own button).
|
||||
// Nothing to configure before a CONFIG arrives: hide the whole column.
|
||||
update_side_visibility(app);
|
||||
if (have_signals && !app.signals_panel_initialised) {
|
||||
app.signals_panel_initialised = true;
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.signal_toggle), TRUE);
|
||||
}
|
||||
if (!have_signals) {
|
||||
app.signals_panel_initialised = false;
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.signal_toggle), FALSE);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.trigger_toggle), FALSE);
|
||||
}
|
||||
app.signal_generation = app.model->generation();
|
||||
|
||||
+3
-1
@@ -195,7 +195,9 @@ void on_trigger_toggled(GtkToggleButton* button, gpointer user) {
|
||||
if (on && signal >= 0) {
|
||||
app->model->set_signal_enabled(static_cast<std::size_t>(signal), true);
|
||||
}
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(app->trigger_revealer), on);
|
||||
// Disabled, not hidden: a panel you can still see is a panel you can read, and the settings you
|
||||
// are about to switch back on must not vanish while you look for them.
|
||||
gtk_widget_set_sensitive(app->trigger_panel, on);
|
||||
// The event-history button is part of trigger mode and the pane it opens is *on demand*; leaving
|
||||
// trigger mode takes the button away and collapses the pane, so going back to streaming is not a
|
||||
// column of stale events beside the plot. Recording is the other half of that answer, so this is
|
||||
|
||||
Reference in new issue
Block a user