ui: a signal's details sit directly under its name; its unit is user-changeable and saved with the session

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-03 11:42:30 +01:00
1 parent bbef7faf3d
commit 87f61ea364
14 files changed
+360 -135

No files matched your search

+71
View File
@@ -918,6 +918,77 @@ void open_calibration(App& app, GtkWindow* parent, std::size_t signal, CalibFiel
}
/// The unit editor: one entry, seeded with the unit now shown, the stream's own unit as its placeholder.
/// An empty entry restores the declared unit (see `ScopeModel::set_signal_unit`).
void open_unit_editor(App& app, GtkWindow* parent, std::size_t signal) {
std::string name;
{
std::lock_guard<std::mutex> lock(app.model->mutex());
if (signal >= app.model->signals().size()) return;
name = app.model->signals()[signal].name;
}
const std::string declared = app.model->declared_unit(signal);
GtkWidget* window = dialog_window(GTK_WIDGET(parent), "Unit");
GtkWidget* box = gtk_window_get_child(GTK_WINDOW(window));
GtkWidget* title = gtk_label_new(name.c_str());
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(box), title);
GtkWidget* entry = gtk_entry_new();
{
std::lock_guard<std::mutex> lock(app.model->mutex());
if (signal < app.model->signals().size()) {
gtk_editable_set_text(GTK_EDITABLE(entry), app.model->signals()[signal].unit.c_str());
}
}
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), declared.empty() ? "(none declared)" : declared.c_str());
gtk_entry_set_activates_default(GTK_ENTRY(entry), true);
gtk_box_append(GTK_BOX(box), labeled_box("unit", entry));
GtkWidget* hint = gtk_label_new("Empty restores the unit the stream declares.");
gtk_widget_add_css_class(hint, "dim-label");
gtk_widget_set_halign(hint, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(box), hint);
struct UnitContext {
App* app;
std::string name;
GtkWidget* entry;
GtkWidget* window;
};
auto* ctx = new UnitContext{&app, name, entry, window};
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
g_signal_connect_swapped(cancel, "clicked", G_CALLBACK(gtk_window_close), window);
GtkWidget* apply = gtk_button_new_with_label("Apply");
g_object_set_data_full(G_OBJECT(apply), "udpscope-ctx", ctx,
[](gpointer p) { delete static_cast<UnitContext*>(p); });
g_signal_connect(apply, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
UnitContext* c = static_cast<UnitContext*>(user);
// Resolved by name at Apply: a CONFIG may have renumbered the signals while the dialog was open.
std::size_t index = std::string::npos;
{
std::lock_guard<std::mutex> lock(c->app->model->mutex());
const std::vector<SignalInfo>& s = c->app->model->signals();
for (std::size_t i = 0; i < s.size(); ++i) {
if (s[i].name == c->name) index = i;
}
}
if (index != std::string::npos) {
std::string text = gtk_editable_get_text(GTK_EDITABLE(c->entry));
const auto b = text.find_first_not_of(" \t");
const auto e = text.find_last_not_of(" \t");
text = b == std::string::npos ? std::string() : text.substr(b, e - b + 1);
c->app->model->set_signal_unit(index, text);
schedule_layout_save(*c->app);
signal_pages_sync(*c->app);
}
gtk_window_destroy(GTK_WINDOW(c->window));
}),
ctx);
dialog_add_button(window, cancel);
dialog_add_button(window, apply);
gtk_window_present(GTK_WINDOW(window));
}
GdkContentProvider* on_drag_prepare(GtkDragSource*, double, double, gpointer user) {
DragContext* ctx = static_cast<DragContext*>(user);
// The payload is the signal index; the channel is resolved on drop.