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:
1 parent
bbef7faf3d
commit
87f61ea364
14 files changed
+360
-135
No files matched your search
+71
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user