ui: the math editor's window, identity row and canvas are a .ui file
The second half of this plan row, and the half of that dialog which is template. src/ui/math-editor.ui holds the window (its size and modality), the box around it, the identity row -- the name and the unit -- and the body with the canvas in it. What stays in code is the drawing: the canvas is a GtkDrawingArea the file *declares* and the code draws, with the gestures that make it a graph editor, and the parameter form follows which node is selected. The palette and the buttons go next. The builder is held on `App` and cleared on the *next* opening rather than with the window: the window belongs to the builder's object list, so freeing the builder under a live window frees the window with it. That is the source form's pattern, and the probe's close-then-reopen path is what exercises it -- `reopened: dialog=ok, nodes=2`. The identity row is read back too, since a `.ui` can hold the widget but not the value: `dialog build: ... name='math3' unit='' ...`, the entries having been filled from the channel being edited. check_ui is at 12 ok, 0 failed.
This commit is contained in:
1 parent
90a17eb186
commit
1a02946656
7 files changed
+123
-36
No files matched your search
+34
-32
@@ -1943,11 +1943,29 @@ void open_math_dialog(App& app, int edit) {
|
||||
}
|
||||
d->selected = d->output;
|
||||
|
||||
GtkWidget* window = gtk_window_new();
|
||||
d->window = window;
|
||||
// The window and its identity row are a `.ui` file (`src/ui/math-editor.ui`). The canvas, the palette and
|
||||
// the parameter form below them stay in code: the canvas is drawn rather than declared, and the form
|
||||
// follows which node is selected.
|
||||
g_clear_object(&app.math_dialog_ui);
|
||||
app.math_dialog_ui = gtk_builder_new_from_resource("/org/udpscope/ui/math-editor.ui");
|
||||
GtkBuilder* ui = app.math_dialog_ui;
|
||||
d->window = GTK_WIDGET(gtk_builder_get_object(ui, "math_window"));
|
||||
GtkWidget* outer = GTK_WIDGET(gtk_builder_get_object(ui, "math_outer"));
|
||||
GtkWidget* head = GTK_WIDGET(gtk_builder_get_object(ui, "math_head"));
|
||||
d->name = GTK_WIDGET(gtk_builder_get_object(ui, "math_name"));
|
||||
d->unit = GTK_WIDGET(gtk_builder_get_object(ui, "math_unit"));
|
||||
if (d->window == nullptr || outer == nullptr || head == nullptr || d->name == nullptr ||
|
||||
d->unit == nullptr) {
|
||||
// A missing id is a mistake in the `.ui`. There is no editor to open, so this says so and gives up
|
||||
// rather than building the rest of a window around a hole; `[math-probe] editor` reads the difference.
|
||||
std::fprintf(stderr, "[math] editor: math-editor.ui is missing an id this code reads\n");
|
||||
delete d;
|
||||
return;
|
||||
}
|
||||
GtkWidget* window = d->window;
|
||||
gtk_window_set_title(GTK_WINDOW(window), edit >= 0 ? "Edit math channel" : "New math channel");
|
||||
gtk_window_set_default_size(GTK_WINDOW(window), 720, 420);
|
||||
gtk_window_set_modal(GTK_WINDOW(window), true);
|
||||
// The dialog's own state, freed with the window: a plain local here would leak the graph and every drag
|
||||
// it is halfway through on each opening.
|
||||
g_object_set_data_full(G_OBJECT(window), "udpscope-math-dialog", d,
|
||||
+[](gpointer p) { delete static_cast<MathDialog*>(p); });
|
||||
g_signal_connect(window, "close-request", G_CALLBACK(+[](GtkWindow* w, gpointer) {
|
||||
@@ -1959,35 +1977,22 @@ void open_math_dialog(App& app, int edit) {
|
||||
}),
|
||||
nullptr);
|
||||
|
||||
GtkWidget* outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_set_margin_top(outer, 10);
|
||||
gtk_widget_set_margin_bottom(outer, 10);
|
||||
gtk_widget_set_margin_start(outer, 10);
|
||||
gtk_widget_set_margin_end(outer, 10);
|
||||
gtk_window_set_child(GTK_WINDOW(window), outer);
|
||||
|
||||
// The channel's own identity: the name is also how another graph names it as a source.
|
||||
GtkWidget* head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
d->name = gtk_entry_new();
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(d->name), "name");
|
||||
// The two values the file cannot hold: what this channel is called and the unit its readouts carry. The
|
||||
// name is also how another graph refers to this channel as a source, which is why it takes the slack.
|
||||
if (edit >= 0 && edit < static_cast<int>(app.math.size())) {
|
||||
gtk_editable_set_text(GTK_EDITABLE(d->name), app.math[static_cast<std::size_t>(edit)].name.c_str());
|
||||
}
|
||||
d->unit = gtk_entry_new();
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(d->unit), "unit (optional)");
|
||||
if (edit >= 0 && edit < static_cast<int>(app.math.size())) {
|
||||
gtk_editable_set_text(GTK_EDITABLE(d->unit), app.math[static_cast<std::size_t>(edit)].unit.c_str());
|
||||
}
|
||||
gtk_widget_set_hexpand(d->name, true);
|
||||
gtk_box_append(GTK_BOX(head), d->name);
|
||||
gtk_box_append(GTK_BOX(head), d->unit);
|
||||
gtk_box_append(GTK_BOX(outer), head);
|
||||
|
||||
GtkWidget* body = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
d->canvas = gtk_drawing_area_new();
|
||||
gtk_widget_set_size_request(d->canvas, 420, 300);
|
||||
gtk_widget_set_hexpand(d->canvas, true);
|
||||
gtk_widget_set_vexpand(d->canvas, true);
|
||||
// The body and the canvas are the file's as well; what is code is the *drawing* -- the function below and
|
||||
// the gestures under it, which are what make the area a graph editor rather than a picture.
|
||||
GtkWidget* body = GTK_WIDGET(gtk_builder_get_object(ui, "math_body"));
|
||||
d->canvas = GTK_WIDGET(gtk_builder_get_object(ui, "math_canvas"));
|
||||
if (body == nullptr || d->canvas == nullptr) {
|
||||
std::fprintf(stderr, "[math] editor: math-editor.ui is missing its body or canvas\n");
|
||||
delete d;
|
||||
return;
|
||||
}
|
||||
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(d->canvas), math_canvas_draw, d, nullptr);
|
||||
GtkGesture* click = gtk_gesture_click_new();
|
||||
g_signal_connect(click, "pressed", G_CALLBACK(+[](GtkGestureClick* g, int, double x, double y,
|
||||
@@ -2028,8 +2033,6 @@ void open_math_dialog(App& app, int edit) {
|
||||
g_signal_connect(keys, "key-pressed", G_CALLBACK(math_canvas_key), d);
|
||||
gtk_widget_add_controller(d->canvas, keys);
|
||||
|
||||
gtk_box_append(GTK_BOX(body), d->canvas);
|
||||
|
||||
// The form: every row exists from the start and is shown or hidden per op, so no rebuild can
|
||||
// invalidate a callback's handle.
|
||||
GtkWidget* form = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
@@ -2181,8 +2184,7 @@ void open_math_dialog(App& app, int edit) {
|
||||
gtk_box_append(GTK_BOX(buttons), fit);
|
||||
gtk_box_append(GTK_BOX(buttons), tidy);
|
||||
gtk_box_append(GTK_BOX(form), buttons);
|
||||
gtk_box_append(GTK_BOX(body), form);
|
||||
gtk_box_append(GTK_BOX(outer), body);
|
||||
gtk_box_append(GTK_BOX(body), form); // the form is the body's second child; the canvas is the file's
|
||||
|
||||
GtkWidget* actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The math channel editor's window and its identity row: the name the channel is called, and the unit its
|
||||
readouts carry.
|
||||
|
||||
The canvas below them is *declared* here but *drawn* by code (`GtkDrawingArea`), and the palette, the
|
||||
parameter form and the buttons around it are code as well, because the form follows the selection: that is
|
||||
the half of this dialog which is not template. The row here is the half that is.
|
||||
|
||||
The window's title is not here: it says which mode the editor is in ("Edit math channel" against "New math
|
||||
channel"), which the code that opens it knows and a file cannot.
|
||||
|
||||
The name takes the slack and the unit does not: the name is also how another graph refers to this channel
|
||||
as a source, so it is the one that should grow as the window does.
|
||||
-->
|
||||
<interface>
|
||||
<object class="GtkWindow" id="math_window">
|
||||
<property name="default-width">720</property>
|
||||
<property name="default-height">420</property>
|
||||
<property name="modal">1</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="math_outer">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">8</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="math_head">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="math_name">
|
||||
<property name="placeholder-text" translatable="yes">name</property>
|
||||
<property name="hexpand">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="math_unit">
|
||||
<property name="placeholder-text" translatable="yes">unit (optional)</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="math_body">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkDrawingArea" id="math_canvas">
|
||||
<property name="width-request">420</property>
|
||||
<property name="height-request">300</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@@ -18,6 +18,7 @@
|
||||
<file preprocess="xml-stripblanks">cursors-panel.ui</file>
|
||||
<file preprocess="xml-stripblanks">measures-panel.ui</file>
|
||||
<file preprocess="xml-stripblanks">math-panel.ui</file>
|
||||
<file preprocess="xml-stripblanks">math-editor.ui</file>
|
||||
<file preprocess="xml-stripblanks">trigger-panel.ui</file>
|
||||
<file preprocess="xml-stripblanks">signals-panel.ui</file>
|
||||
</gresource>
|
||||
|
||||
@@ -246,6 +246,10 @@ struct App {
|
||||
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 --
|
||||
/// freeing it under a live window frees the window with it. That is the source form's pattern.
|
||||
GtkBuilder* math_dialog_ui = nullptr;
|
||||
GtkWidget* math_dialog = nullptr;
|
||||
/// One value label per measure, in `measures` order; rebuilt with the list.
|
||||
std::vector<GtkWidget*> measure_labels;
|
||||
|
||||
+10
-3
@@ -389,6 +389,12 @@ gboolean math_probe(gpointer user) {
|
||||
const std::size_t built = ed->nodes.size();
|
||||
const double gain_read = ed->nodes.empty() ? 0.0 : ed->nodes.back().gain;
|
||||
const double offset_read = ed->nodes.empty() ? 0.0 : ed->nodes.back().offset;
|
||||
// The identity row, which the file cannot hold values for: the entries were filled from the channel
|
||||
// being edited, so reading them back is what shows the row is the same one the code writes.
|
||||
const std::string name_read =
|
||||
ed->name != nullptr ? gtk_editable_get_text(GTK_EDITABLE(ed->name)) : "-";
|
||||
const std::string unit_read =
|
||||
ed->unit != nullptr ? gtk_editable_get_text(GTK_EDITABLE(ed->unit)) : "-";
|
||||
on_math_apply(nullptr, ed); // frees the dialog: nothing below may touch `ed`
|
||||
const std::size_t saved = a->math.empty()
|
||||
? 0u
|
||||
@@ -397,9 +403,10 @@ gboolean math_probe(gpointer user) {
|
||||
a->math.empty() ? -1 : a->math[static_cast<std::size_t>(index)].output;
|
||||
std::fprintf(stderr,
|
||||
"[math-probe] dialog build: nodes=%zu, output after add=%d (want %zu); "
|
||||
"reading back gain=%.2f offset=%.2f; saved nodes=%zu output=%d\n",
|
||||
built, output_after_add, built - 1, gain_read, offset_read, saved,
|
||||
output_saved);
|
||||
"reading back gain=%.2f offset=%.2f; name='%s' unit='%s'; "
|
||||
"saved nodes=%zu output=%d\n",
|
||||
built, output_after_add, built - 1, gain_read, offset_read, name_read.c_str(),
|
||||
unit_read.c_str(), saved, output_saved);
|
||||
}
|
||||
}
|
||||
// Closed and reopened: `gtk_window_destroy()` does not emit `close-request`, so a dialog closed
|
||||
|
||||
Reference in new issue
Block a user