ui: the readouts show in a strip under the plot when the dock is hidden
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
54f2722fea
commit
c8d9d04f59
7 files changed
+282
-22
No files matched your search
+137
-17
@@ -9,6 +9,12 @@
|
||||
// so the two views cannot disagree. Every label is ellipsized at a fixed `width-chars`, so the dock's
|
||||
// layout does not depend on what a value reads; the full text is the label's tooltip.
|
||||
//
|
||||
// The same strings are shown a third time in the readouts strip under the plot (`readouts_strip`, one
|
||||
// fixed-height line: a "Cursors" button, one chip per cursor line with its delta, a "Measures" button, one
|
||||
// chip per measurement with its value), which is on screen exactly when the dock is not. The setters below
|
||||
// write the strip's labels in the same call as the dock's, from the same argument, so there is still one
|
||||
// string and no second formatter.
|
||||
//
|
||||
// Below them, the Events block: one compact row per cached event (marker, title, time; no thumbnails),
|
||||
// a Manage button for the full list, and one click on a row to replay it. Its rows are built from the
|
||||
// same `event_store->list()` and the same `event_row_title()` as the events page, so the two cannot
|
||||
@@ -72,6 +78,46 @@ void on_measure_line_pressed(GtkGestureClick* gesture, int n_press, double, doub
|
||||
open_measure_dialog(*static_cast<App*>(user), idx);
|
||||
}
|
||||
|
||||
/// The page's `who` cell, verbatim, onto a copy: a chip and a name as markup, or plain text, and its
|
||||
/// tooltip. The dock's cell and the strip's chip are both made by it.
|
||||
void copy_who(GtkWidget* dst, GtkWidget* src) {
|
||||
if (src == nullptr) return;
|
||||
const char* text = gtk_label_get_label(GTK_LABEL(src));
|
||||
if (gtk_label_get_use_markup(GTK_LABEL(src))) {
|
||||
gtk_label_set_markup(GTK_LABEL(dst), text);
|
||||
} else {
|
||||
gtk_label_set_text(GTK_LABEL(dst), text);
|
||||
}
|
||||
const char* tip = gtk_widget_get_tooltip_text(src);
|
||||
gtk_widget_set_tooltip_text(dst, tip != nullptr ? tip : gtk_label_get_text(GTK_LABEL(src)));
|
||||
}
|
||||
|
||||
void clear_box(GtkWidget* box) {
|
||||
GtkWidget* child = gtk_widget_get_first_child(box);
|
||||
while (child != nullptr) {
|
||||
GtkWidget* next = gtk_widget_get_next_sibling(child);
|
||||
gtk_box_remove(GTK_BOX(box), child);
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// The strip's cell widths, in characters. Fixed like the dock's, so the strip's chips are as wide with
|
||||
/// `--` as with `-123.456 mV` (what does not fit scrolls).
|
||||
constexpr int kStripWhoChars = 6;
|
||||
constexpr int kStripTypeChars = 10;
|
||||
|
||||
/// A flat text button that opens a slide-over page. It only opens a page, so a rebuild can never free the
|
||||
/// widget its own signal was emitted from.
|
||||
GtkWidget* strip_button(const char* label, GCallback on_click, App* app, const char* tip) {
|
||||
GtkWidget* b = gtk_button_new_with_label(label);
|
||||
gtk_widget_add_css_class(b, "flat");
|
||||
gtk_widget_add_css_class(b, "udpscope-strip-button");
|
||||
gtk_widget_set_valign(b, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_tooltip_text(b, tip);
|
||||
g_signal_connect(b, "clicked", on_click, app);
|
||||
return b;
|
||||
}
|
||||
|
||||
/// A section's heading row: its title at the left, its Edit button at the right.
|
||||
GtkWidget* section_head(const char* title, GtkWidget** edit_out, GCallback on_edit, App* app,
|
||||
const char* tip) {
|
||||
@@ -101,6 +147,25 @@ void dock_cursors_header(GtkGrid* grid) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills the strip `main-window.ui` declares: the two buttons and the two boxes the rebuilds fill.
|
||||
void build_readouts_strip(App& app) {
|
||||
if (app.strip_box == nullptr) return;
|
||||
clear_box(app.strip_box);
|
||||
app.strip_cursor_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
app.strip_measure_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_box_append(GTK_BOX(app.strip_box),
|
||||
strip_button("Cursors", G_CALLBACK(on_edit_cursors), &app, "the cursors' full panel"));
|
||||
gtk_box_append(GTK_BOX(app.strip_box), app.strip_cursor_box);
|
||||
GtkWidget* sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
||||
gtk_widget_set_margin_top(sep, 5);
|
||||
gtk_widget_set_margin_bottom(sep, 5);
|
||||
gtk_box_append(GTK_BOX(app.strip_box), sep);
|
||||
gtk_box_append(GTK_BOX(app.strip_box),
|
||||
strip_button("Measures", G_CALLBACK(on_edit_measures), &app,
|
||||
"the measurements' full panel"));
|
||||
gtk_box_append(GTK_BOX(app.strip_box), app.strip_measure_box);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GtkWidget* build_readouts_dock(App& app) {
|
||||
@@ -130,6 +195,7 @@ GtkWidget* build_readouts_dock(App& app) {
|
||||
gtk_box_append(GTK_BOX(box), app.dock_measures_box);
|
||||
|
||||
app.dock_readouts = box;
|
||||
build_readouts_strip(app);
|
||||
// The tables are built from the pages' current lines, which exist already when the pages were built
|
||||
// first; otherwise the pages' own rebuild fills them.
|
||||
dock_rebuild_cursors(app);
|
||||
@@ -147,22 +213,17 @@ void dock_rebuild_cursors(App& app) {
|
||||
child = next;
|
||||
}
|
||||
app.dock_cursor_lines.clear();
|
||||
// The strip's chips are rebuilt with the dock's lines (one per line, same order), so the counts agree by
|
||||
// construction. The box itself keeps its sensitivity across the rebuild.
|
||||
if (app.strip_cursor_box != nullptr) clear_box(app.strip_cursor_box);
|
||||
app.strip_cursor_who.clear();
|
||||
app.strip_cursor_deltas.clear();
|
||||
dock_cursors_header(grid);
|
||||
int at = 1;
|
||||
for (const App::CursorLine& src : app.cursors_lines) {
|
||||
App::DockCursorLine line;
|
||||
line.label = fixed_label("", kNameChars, GTK_ALIGN_START);
|
||||
if (src.label != nullptr) {
|
||||
// The page's `who` cell, verbatim: a chip and a name as markup, or plain text.
|
||||
const char* text = gtk_label_get_label(GTK_LABEL(src.label));
|
||||
if (gtk_label_get_use_markup(GTK_LABEL(src.label))) {
|
||||
gtk_label_set_markup(GTK_LABEL(line.label), text);
|
||||
} else {
|
||||
gtk_label_set_text(GTK_LABEL(line.label), text);
|
||||
}
|
||||
const char* tip = gtk_widget_get_tooltip_text(src.label);
|
||||
gtk_widget_set_tooltip_text(line.label, tip != nullptr ? tip : gtk_label_get_text(GTK_LABEL(src.label)));
|
||||
}
|
||||
copy_who(line.label, src.label);
|
||||
gtk_grid_attach(grid, line.label, 0, at, 1, 1);
|
||||
GtkWidget** cells[3] = {&line.a, &line.b, &line.d};
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
@@ -173,25 +234,41 @@ void dock_rebuild_cursors(App& app) {
|
||||
*cells[c] = v;
|
||||
}
|
||||
app.dock_cursor_lines.push_back(line);
|
||||
if (app.strip_cursor_box != nullptr) {
|
||||
// A chip: the line's `who` and its delta. Not selectable, so a drag on it scrolls the strip.
|
||||
GtkWidget* chip = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
GtkWidget* who = fixed_label("", kStripWhoChars, GTK_ALIGN_START);
|
||||
copy_who(who, src.label);
|
||||
GtkWidget* delta = fixed_label("", kValueChars, GTK_ALIGN_END);
|
||||
gtk_widget_add_css_class(delta, "numeric");
|
||||
gtk_box_append(GTK_BOX(chip), who);
|
||||
gtk_box_append(GTK_BOX(chip), delta);
|
||||
gtk_box_append(GTK_BOX(app.strip_cursor_box), chip);
|
||||
app.strip_cursor_who.push_back(who);
|
||||
app.strip_cursor_deltas.push_back(delta);
|
||||
}
|
||||
++at;
|
||||
}
|
||||
}
|
||||
|
||||
void dock_rebuild_measures(App& app) {
|
||||
if (app.dock_measures_box == nullptr) return;
|
||||
GtkWidget* child = gtk_widget_get_first_child(app.dock_measures_box);
|
||||
while (child != nullptr) {
|
||||
GtkWidget* next = gtk_widget_get_next_sibling(child);
|
||||
gtk_box_remove(GTK_BOX(app.dock_measures_box), child);
|
||||
child = next;
|
||||
}
|
||||
clear_box(app.dock_measures_box);
|
||||
if (app.strip_measure_box != nullptr) clear_box(app.strip_measure_box);
|
||||
app.dock_measure_labels.clear();
|
||||
app.strip_measure_labels.clear();
|
||||
if (app.measures.empty()) {
|
||||
GtkWidget* none = gtk_label_new("no measurements");
|
||||
gtk_widget_set_halign(none, GTK_ALIGN_START);
|
||||
gtk_widget_add_css_class(none, "caption");
|
||||
gtk_widget_add_css_class(none, "dim-label");
|
||||
gtk_box_append(GTK_BOX(app.dock_measures_box), none);
|
||||
if (app.strip_measure_box != nullptr) {
|
||||
GtkWidget* strip_none = gtk_label_new("no measurements");
|
||||
gtk_widget_add_css_class(strip_none, "caption");
|
||||
gtk_widget_add_css_class(strip_none, "dim-label");
|
||||
gtk_box_append(GTK_BOX(app.strip_measure_box), strip_none);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (std::size_t i = 0; i < app.measures.size(); ++i) {
|
||||
@@ -217,6 +294,29 @@ void dock_rebuild_measures(App& app) {
|
||||
gtk_widget_add_controller(line, GTK_EVENT_CONTROLLER(click));
|
||||
gtk_box_append(GTK_BOX(app.dock_measures_box), line);
|
||||
app.dock_measure_labels.push_back(value);
|
||||
|
||||
if (app.strip_measure_box != nullptr) {
|
||||
// The strip's chip: the signal's chip, the type's short name and the value; a click on it opens the
|
||||
// same dialog as the dock's line.
|
||||
GtkWidget* sline = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
g_object_set_data(G_OBJECT(sline), "udpscope-measure-index",
|
||||
GINT_TO_POINTER(static_cast<int>(i) + 1));
|
||||
GtkWidget* schip = gtk_label_new("");
|
||||
gtk_label_set_markup(GTK_LABEL(schip), measure_chip_markup(app, m.signal).c_str());
|
||||
gtk_widget_set_tooltip_text(schip, m.signal.c_str());
|
||||
gtk_box_append(GTK_BOX(sline), schip);
|
||||
GtkWidget* skind = fixed_label(measure_type_name(m.type), kStripTypeChars, GTK_ALIGN_START);
|
||||
gtk_widget_set_tooltip_text(skind, (m.signal + "\n" + measure_type_name(m.type)).c_str());
|
||||
gtk_box_append(GTK_BOX(sline), skind);
|
||||
GtkWidget* svalue = fixed_label("--", kValueChars, GTK_ALIGN_END);
|
||||
gtk_widget_add_css_class(svalue, "numeric");
|
||||
gtk_box_append(GTK_BOX(sline), svalue);
|
||||
GtkGesture* sclick = gtk_gesture_click_new();
|
||||
g_signal_connect(sclick, "pressed", G_CALLBACK(on_measure_line_pressed), &app);
|
||||
gtk_widget_add_controller(sline, GTK_EVENT_CONTROLLER(sclick));
|
||||
gtk_box_append(GTK_BOX(app.strip_measure_box), sline);
|
||||
app.strip_measure_labels.push_back(svalue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,11 +327,31 @@ void dock_cursor_cell(App& app, std::size_t line, const std::string& a, const st
|
||||
set_value_text(l.a, a);
|
||||
set_value_text(l.b, b);
|
||||
set_value_text(l.d, d);
|
||||
// The strip's chip takes the delta from the same argument, in the same call.
|
||||
if (line < app.strip_cursor_deltas.size()) set_value_text(app.strip_cursor_deltas[line], d);
|
||||
}
|
||||
|
||||
void dock_measure_value(App& app, std::size_t i, const std::string& text) {
|
||||
if (i >= app.dock_measure_labels.size()) return; // mid-rebuild
|
||||
set_value_text(app.dock_measure_labels[i], text);
|
||||
if (i < app.strip_measure_labels.size()) set_value_text(app.strip_measure_labels[i], text);
|
||||
}
|
||||
|
||||
void dock_set_cursors_sensitive(App& app, bool on) {
|
||||
// Disabled, not hidden: the dock's grid and the strip's chips go grey together and read `--`.
|
||||
if (app.dock_cursor_grid != nullptr) gtk_widget_set_sensitive(app.dock_cursor_grid, on);
|
||||
if (app.strip_cursor_box != nullptr) gtk_widget_set_sensitive(app.strip_cursor_box, on);
|
||||
}
|
||||
|
||||
void sync_readouts_strip(App& app) {
|
||||
if (app.readouts_strip == nullptr || app.side_view == nullptr) return;
|
||||
// The strip is on screen exactly when the dock is not: `show-sidebar` is false when the dock is hidden
|
||||
// with the Dock button, and when the window is collapsed and the dock is not currently overlaid.
|
||||
const bool want =
|
||||
!adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(app.side_view));
|
||||
if ((gtk_widget_get_visible(app.readouts_strip) != FALSE) != want) {
|
||||
gtk_widget_set_visible(app.readouts_strip, want);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Reference in new issue
Block a user