diff --git a/src/ui.cpp b/src/ui.cpp index 20e4b4d..2db9862 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -4186,12 +4186,21 @@ gboolean on_app_key_pressed(GtkEventControllerKey*, guint keyval, guint, GdkModi /// /// [ 2 s ▾ ] [plots ▾] ............... [pause] [trigger] [panel] [menu] GtkWidget* build_toolbar(App& app) { - GtkWidget* bar = gtk_header_bar_new(); + // The bar's frame is a `.ui` file (`src/ui/header-bar.ui`): the bar, the slot the window control goes + // in, the trigger/pause/events group with its separator, and the five controls at the far end. Its + // *order* is the file's too, which is what three comments here used to explain. + GtkBuilder* ui = ui_load(app, "/org/udpscope/ui/header-bar.ui"); + GtkWidget* bar = GTK_WIDGET(gtk_builder_get_object(ui, "header_bar")); + if (bar == nullptr) return gtk_header_bar_new(); app.toolbar = bar; // The control already *is* the window's name: a `window` label in front of it said nothing the - // `2 s` beside it did not, and the bar is the busiest row in the window. - gtk_header_bar_pack_start(GTK_HEADER_BAR(bar), build_window_control(app)); + // `2 s` beside it did not, and the bar is the busiest row in the window. It goes into the file's slot + // rather than straight onto the bar: `gtk_header_bar_pack_start()` appends *after* whatever the file + // has already packed, so a control added here would land right of the whole group. + if (GtkWidget* slot = GTK_WIDGET(gtk_builder_get_object(ui, "window_control_slot"))) { + gtk_box_append(GTK_BOX(slot), build_window_control(app)); + } refresh_window_limits(app); // The end group reads, left to right: cursors, measurements, math, menu -- with the @@ -4280,85 +4289,34 @@ GtkWidget* build_toolbar(App& app) { gtk_box_append(GTK_BOX(menu_box), about_item); // --- the pane toggles, which live on the bar rather than in the menu ------ - // Created here because they share the popover's styling helpers; packed onto the header bar - // below, where the pack order (not this order) decides where they sit. - app.measures_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.measures_toggle), "udpscope-measure-symbolic"); - gtk_widget_set_tooltip_text(app.measures_toggle, - "compute a value over the visible window of a signal, and " - "optionally mark it on the plot"); - g_signal_connect(app.measures_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* b, - gpointer user) { - App* a = static_cast(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); - - app.math_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.math_toggle), "udpscope-math-symbolic"); - gtk_widget_set_tooltip_text(app.math_toggle, - "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"); - g_signal_connect(app.math_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* b, - gpointer user) { - App* a = static_cast(user); - if (a->math_revealer != nullptr) { - gtk_revealer_set_reveal_child(GTK_REVEALER(a->math_revealer), - gtk_toggle_button_get_active(b)); - } - }), - &app); - - app.cursors_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.cursors_toggle), "udpscope-cursors-symbolic"); - gtk_widget_set_tooltip_text(app.cursors_toggle, - "the cursor instruments: two times, two levels, or the two read " - "off each trace where they cross"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.cursors_toggle), false); - g_signal_connect(app.cursors_toggle, "toggled", G_CALLBACK(on_cursors_toggled), &app); + // The three pane toggles, and the rest of the bar, are the file's now; their handlers are wired where + // the bar's ids are resolved, below. The popover above still shares this function's styling helpers + // (`menu_button()`, `menu_toggle()`), which is why it is built here. GtkWidget* popover = gtk_popover_new(); gtk_widget_add_css_class(popover, "udpscope-menu"); gtk_popover_set_child(GTK_POPOVER(popover), menu_box); - app.menu_button = gtk_menu_button_new(); - gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(app.menu_button), "open-menu-symbolic"); + app.menu_button = GTK_WIDGET(gtk_builder_get_object(ui, "menu_button")); + app.sidebar_toggle = GTK_WIDGET(gtk_builder_get_object(ui, "sidebar_toggle")); + 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) { + // 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; + } gtk_menu_button_set_popover(GTK_MENU_BUTTON(app.menu_button), popover); - gtk_widget_set_tooltip_text(app.menu_button, "view options"); - // `pack_end()` lays its children out right to left -- the first packed is the *rightmost* -- so - // the menu is packed first to sit last on screen, and the pane toggles before it in reverse of the - // order they should read in: cursors, measurements, math, menu. - // --- what stays on the bar (packed end: first call is rightmost) ---------- - app.sidebar_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.sidebar_toggle), "sidebar-show-symbolic"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(app.sidebar_toggle), true); - gtk_widget_set_tooltip_text(app.sidebar_toggle, "show the side panel"); + // What each control *does*, which a file cannot hold. g_signal_connect(app.sidebar_toggle, "toggled", G_CALLBACK(on_sidebar_toggled), &app); - - app.trigger_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.trigger_toggle), "udpscope-trigger-symbolic"); - gtk_widget_set_tooltip_text(app.trigger_toggle, - "trigger mode: freeze the view and show one captured event " - "at a time"); g_signal_connect(app.trigger_toggle, "toggled", G_CALLBACK(on_trigger_toggled), &app); - // Packed with the window control instead of at the far end: the trigger and the pause are read - // against the *span* on screen (a window change moves the trigger's event window with it), so they - // belong next to the control that sets it. - gtk_header_bar_pack_start(GTK_HEADER_BAR(bar), app.trigger_toggle); - - // Immediately right of the trigger, and only while it is on: the event history is a *trigger* - // thing, and asking for it is one click from the mode that produces it. - app.events_toggle = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.events_toggle), "udpscope-events-symbolic"); - gtk_widget_set_tooltip_text(app.events_toggle, - "the event history: the triggered acquisitions kept in the cache, " - "with a picture of each"); - gtk_widget_set_visible(app.events_toggle, FALSE); + // The event history is a *trigger* thing, and one click from the mode that produces it. g_signal_connect(app.events_toggle, "toggled", G_CALLBACK(+[](GtkToggleButton* b, gpointer user) { App* a = static_cast(user); const bool open = gtk_toggle_button_get_active(b); @@ -4369,31 +4327,26 @@ GtkWidget* build_toolbar(App& app) { if (open) refresh_events_pane(*a); }), &app); - - - app.pause_button = gtk_toggle_button_new(); - gtk_button_set_icon_name(GTK_BUTTON(app.pause_button), "media-playback-pause-symbolic"); - gtk_widget_set_tooltip_text(app.pause_button, "pause acquisition"); g_signal_connect(app.pause_button, "toggled", G_CALLBACK(on_pause_toggled), &app); - gtk_header_bar_pack_start(GTK_HEADER_BAR(bar), app.pause_button); - // A separator, because the events button is not another mode on the plot: what stands to its left is - // what the *view* is doing (the window, the trigger, the pause) and what follows is the cache of what - // those produced. - gtk_header_bar_pack_start(GTK_HEADER_BAR(bar), gtk_separator_new(GTK_ORIENTATION_VERTICAL)); - gtk_header_bar_pack_start(GTK_HEADER_BAR(bar), app.events_toggle); - - // Packed here, after every widget above has been created: the *pack* order is what decides the - // layout, not the creation order, and packing before the widget exists passes NULL to the header - // bar (five `gtk_box_prepend: assertion` criticals, and the controls simply absent). - // The **first** packed is the rightmost, and the dump settled it: its tree order is the visual - // order left to right, and it reads the *reverse* of this list. (Both of this comment's earlier - // versions were wrong in turn; the arrangement they produced is what got reported.) Packed in this - // order the row reads, left to right: signal list, cursors, measurements, math, sidebar, menu. - gtk_header_bar_pack_end(GTK_HEADER_BAR(bar), app.menu_button); - gtk_header_bar_pack_end(GTK_HEADER_BAR(bar), app.sidebar_toggle); - gtk_header_bar_pack_end(GTK_HEADER_BAR(bar), app.math_toggle); - gtk_header_bar_pack_end(GTK_HEADER_BAR(bar), app.measures_toggle); - gtk_header_bar_pack_end(GTK_HEADER_BAR(bar), app.cursors_toggle); + g_signal_connect(app.measures_toggle, "toggled", + G_CALLBACK(+[](GtkToggleButton* b, gpointer user) { + App* a = static_cast(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(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; } diff --git a/src/ui/header-bar.ui b/src/ui/header-bar.ui new file mode 100644 index 0000000..b1bd8ef --- /dev/null +++ b/src/ui/header-bar.ui @@ -0,0 +1,87 @@ + + + + + + + + + horizontal + + + + + udpscope-trigger-symbolic + trigger mode: freeze the view and show one captured event at a time + + + + + media-playback-pause-symbolic + pause acquisition + + + + + vertical + + + + + udpscope-events-symbolic + the event history: the triggered acquisitions kept in the cache, with a picture of each + 0 + + + + + open-menu-symbolic + view options + + + + + sidebar-show-symbolic + show the side panel + 1 + + + + + udpscope-math-symbolic + 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 + + + + + udpscope-measure-symbolic + compute a value over the visible window of a signal, and optionally mark it on the plot + + + + + udpscope-cursors-symbolic + the cursor instruments: two times, two levels, or the two read off each trace where they cross + 0 + + + + diff --git a/src/ui/udpscope-ui.gresource.xml b/src/ui/udpscope-ui.gresource.xml index 7fd5c92..6dc9310 100644 --- a/src/ui/udpscope-ui.gresource.xml +++ b/src/ui/udpscope-ui.gresource.xml @@ -20,6 +20,7 @@ math-panel.ui math-editor.ui events-pane.ui + header-bar.ui trigger-panel.ui signals-panel.ui