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
+9
-2
@@ -4013,6 +4013,11 @@ gboolean tick(gpointer user) {
|
|||||||
app->sidebar_syncing = false;
|
app->sidebar_syncing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The readouts strip is on screen exactly when the dock is not. Decided here, right after the wish was
|
||||||
|
// re-asserted and *before* the collapsing below (which makes the split view write `show-sidebar`
|
||||||
|
// false itself until the next frame re-asserts it): read after it, the strip would show for one frame
|
||||||
|
// each time the window narrowed.
|
||||||
|
sync_readouts_strip(*app);
|
||||||
const int window_w = gtk_widget_get_width(app->window);
|
const int window_w = gtk_widget_get_width(app->window);
|
||||||
if (window_w > 0) {
|
if (window_w > 0) {
|
||||||
adw_overlay_split_view_set_collapsed(ADW_OVERLAY_SPLIT_VIEW(app->side_view),
|
adw_overlay_split_view_set_collapsed(ADW_OVERLAY_SPLIT_VIEW(app->side_view),
|
||||||
@@ -4976,6 +4981,8 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
app->badge_bar = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_bar"));
|
app->badge_bar = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_bar"));
|
||||||
app->badge_signals = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_signals"));
|
app->badge_signals = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_signals"));
|
||||||
app->badge_system = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_system"));
|
app->badge_system = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "badge_system"));
|
||||||
|
app->readouts_strip = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "readouts_strip"));
|
||||||
|
app->strip_box = GTK_WIDGET(gtk_builder_get_object(app->window_ui, "readouts_strip_box"));
|
||||||
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
// A missing id is the one mistake neither the build nor `GtkBuilder` sees, and every use below would
|
||||||
// be a null pointer.
|
// be a null pointer.
|
||||||
if (app->window == nullptr || side_view == nullptr ||
|
if (app->window == nullptr || side_view == nullptr ||
|
||||||
@@ -4983,7 +4990,7 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
app->status_badge == nullptr ||
|
app->status_badge == nullptr ||
|
||||||
app->status_label == nullptr || app->status_info_button == nullptr ||
|
app->status_label == nullptr || app->status_info_button == nullptr ||
|
||||||
app->status_row == nullptr || app->badge_bar == nullptr || app->badge_signals == nullptr ||
|
app->status_row == nullptr || app->badge_bar == nullptr || app->badge_signals == nullptr ||
|
||||||
app->badge_system == nullptr ||
|
app->badge_system == nullptr || app->readouts_strip == nullptr || app->strip_box == nullptr ||
|
||||||
side == nullptr || app->dock_scroller == nullptr || app->dock_page == nullptr) {
|
side == nullptr || app->dock_scroller == nullptr || app->dock_page == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -5165,7 +5172,7 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
// switch that turns it back on. The trigger's body is deliberately *not* disabled: a trigger is
|
// switch that turns it back on. The trigger's body is deliberately *not* disabled: a trigger is
|
||||||
// configured before it is switched on, so its settings have to stay editable while it is off.
|
// configured before it is switched on, so its settings have to stay editable while it is off.
|
||||||
gtk_widget_set_sensitive(app->cursors_body, FALSE);
|
gtk_widget_set_sensitive(app->cursors_body, FALSE);
|
||||||
gtk_widget_set_sensitive(app->dock_cursor_grid, FALSE);
|
dock_set_cursors_sensitive(*app, false); // the dock's grid and the strip's chips, together
|
||||||
// The arm controls are the one part of the trigger pane that needs trigger mode running: both of them,
|
// The arm controls are the one part of the trigger pane that needs trigger mode running: both of them,
|
||||||
// and the compact line's visibility, are stated from the toggle by `sync_arm_buttons()`.
|
// and the compact line's visibility, are stated from the toggle by `sync_arm_buttons()`.
|
||||||
sync_arm_buttons(*app);
|
sync_arm_buttons(*app);
|
||||||
|
|||||||
+25
-1
@@ -4,7 +4,8 @@
|
|||||||
<!--
|
<!--
|
||||||
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
The main window's skeleton: the window, the split view that puts the sidebar beside the plot, the
|
||||||
plot column (`left_column`: `plot_overlay` > `plot_box`, the slide-over panel being an overlay child added
|
plot column (`left_column`: `plot_overlay` > `plot_box`, the slide-over panel being an overlay child added
|
||||||
in code) with the badge bar and the status line under it, and the sidebar, which is the dock.
|
in code) with the readouts strip (only while the dock is not on screen), the badge bar and the status
|
||||||
|
line under it, and the sidebar, which is the dock.
|
||||||
|
|
||||||
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
|
The sidebar is an `AdwOverlaySplitView`, which is what a utility sidebar is in GNOME (as against
|
||||||
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
|
`AdwNavigationSplitView`, which is for navigation lists). Three things follow from that, and they are
|
||||||
@@ -96,6 +97,29 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<!-- The readouts strip: the cursors' deltas and the measurements' values on one line, shown
|
||||||
|
only while the dock is not (hidden with the Dock button, or collapsed and not currently
|
||||||
|
overlaid; `sync_readouts_strip()` decides, per tick). A fixed height, so the content
|
||||||
|
never sets it; what does not fit scrolls horizontally with no bar taking height. The
|
||||||
|
box inside is filled by code (`src/ui_dock.cpp`). Starts hidden: the dock starts shown. -->
|
||||||
|
<object class="GtkScrolledWindow" id="readouts_strip">
|
||||||
|
<property name="visible">0</property>
|
||||||
|
<property name="height-request">26</property>
|
||||||
|
<property name="vexpand">0</property>
|
||||||
|
<property name="hscrollbar-policy">external</property>
|
||||||
|
<property name="vscrollbar-policy">never</property>
|
||||||
|
<property name="margin-start">6</property>
|
||||||
|
<property name="margin-end">6</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkBox" id="readouts_strip_box">
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
<property name="spacing">8</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<!-- Setup is reached by the object it configures: a badge per signal on the left
|
<!-- Setup is reached by the object it configures: a badge per signal on the left
|
||||||
(step 2), the system badges (Trigger, Source) on the right. A fixed height, so
|
(step 2), the system badges (Trigger, Source) on the right. A fixed height, so
|
||||||
|
|||||||
@@ -610,6 +610,17 @@ struct App {
|
|||||||
GtkWidget* dock_measures_edit = nullptr;
|
GtkWidget* dock_measures_edit = nullptr;
|
||||||
/// One value label per measurement, in `measures` order (the dock's copy of `measure_labels`).
|
/// One value label per measurement, in `measures` order (the dock's copy of `measure_labels`).
|
||||||
std::vector<GtkWidget*> dock_measure_labels;
|
std::vector<GtkWidget*> dock_measure_labels;
|
||||||
|
/// The readouts strip under the plot (`main-window.ui` `readouts_strip`, `src/ui_dock.cpp`): shown only
|
||||||
|
/// while the dock is not. Its value labels mirror the dock's one for one -- `strip_cursor_deltas` the
|
||||||
|
/// `d` cell of each `dock_cursor_lines` entry, `strip_measure_labels` each of `dock_measure_labels` --
|
||||||
|
/// and `strip_cursor_who` the cursor lines' `who` cells, all written from the dock setters' arguments.
|
||||||
|
GtkWidget* readouts_strip = nullptr;
|
||||||
|
GtkWidget* strip_box = nullptr;
|
||||||
|
GtkWidget* strip_cursor_box = nullptr; ///< the cursor chips; insensitive with the cursors off
|
||||||
|
GtkWidget* strip_measure_box = nullptr; ///< the measurement chips
|
||||||
|
std::vector<GtkWidget*> strip_cursor_who;
|
||||||
|
std::vector<GtkWidget*> strip_cursor_deltas;
|
||||||
|
std::vector<GtkWidget*> strip_measure_labels;
|
||||||
/// The measurements the user set up, and what they last computed. The list is the state;
|
/// The measurements the user set up, and what they last computed. The list is the state;
|
||||||
/// the results are recomputed on a throttle (the numbers are window statistics, and the
|
/// the results are recomputed on a throttle (the numbers are window statistics, and the
|
||||||
/// window scrolls every frame -- 60 recomputations a second of every sample is not a
|
/// window scrolls every frame -- 60 recomputations a second of every sample is not a
|
||||||
@@ -1784,6 +1795,13 @@ void dock_rebuild_measures(App& app);
|
|||||||
void dock_cursor_cell(App& app, std::size_t line, const std::string& a, const std::string& b,
|
void dock_cursor_cell(App& app, std::size_t line, const std::string& a, const std::string& b,
|
||||||
const std::string& d);
|
const std::string& d);
|
||||||
void dock_measure_value(App& app, std::size_t i, const std::string& text);
|
void dock_measure_value(App& app, std::size_t i, const std::string& text);
|
||||||
|
/// The readouts strip under the plot (`src/ui_dock.cpp`): the third reader of the same strings. It is
|
||||||
|
/// filled by the `dock_*` rebuilds and written by the `dock_*` setters above (so nothing here formats),
|
||||||
|
/// and shown exactly when the dock is not: `sync_readouts_strip()` is called by the tick after the
|
||||||
|
/// show-sidebar reconciliation. `dock_set_cursors_sensitive()` is the one writer of the cursor readouts'
|
||||||
|
/// sensitivity, for the dock's grid and the strip's chips together.
|
||||||
|
void sync_readouts_strip(App& app);
|
||||||
|
void dock_set_cursors_sensitive(App& app, bool on);
|
||||||
/// The Events block under the readouts (`src/ui_dock.cpp`): compact rows (marker, title, time; no
|
/// The Events block under the readouts (`src/ui_dock.cpp`): compact rows (marker, title, time; no
|
||||||
/// thumbnails) from `event_store->list()`, newest first, and a Manage button for the `events` page.
|
/// thumbnails) from `event_store->list()`, newest first, and a Manage button for the `events` page.
|
||||||
/// `dock_rebuild_events()` follows every change of what is *cached*, `dock_mark_events()` every change
|
/// `dock_rebuild_events()` follows every change of what is *cached*, `dock_mark_events()` every change
|
||||||
|
|||||||
+3
-2
@@ -151,8 +151,9 @@ void on_cursors_toggled(GtkToggleButton* button, gpointer user) {
|
|||||||
// cursors back on -- and a control inside an insensitive parent cannot be clicked.
|
// cursors back on -- and a control inside an insensitive parent cannot be clicked.
|
||||||
gtk_widget_set_sensitive(app->cursors_reset, on);
|
gtk_widget_set_sensitive(app->cursors_reset, on);
|
||||||
if (app->cursors_body != nullptr) gtk_widget_set_sensitive(app->cursors_body, on);
|
if (app->cursors_body != nullptr) gtk_widget_set_sensitive(app->cursors_body, on);
|
||||||
// The dock's copy follows: switched off it stays on screen, insensitive, reading `--`.
|
// The dock's copy and the strip's chips follow: switched off they stay on screen, insensitive, reading
|
||||||
if (app->dock_cursor_grid != nullptr) gtk_widget_set_sensitive(app->dock_cursor_grid, on);
|
// `--`. One function, so the two cannot disagree.
|
||||||
|
dock_set_cursors_sensitive(*app, on);
|
||||||
if (!on) update_cursors_pane(*app);
|
if (!on) update_cursors_pane(*app);
|
||||||
if (on) {
|
if (on) {
|
||||||
app->rows->reset_cursors();
|
app->rows->reset_cursors();
|
||||||
|
|||||||
+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
|
// 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.
|
// 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),
|
// 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
|
// 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
|
// 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);
|
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.
|
/// 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,
|
GtkWidget* section_head(const char* title, GtkWidget** edit_out, GCallback on_edit, App* app,
|
||||||
const char* tip) {
|
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
|
} // namespace
|
||||||
|
|
||||||
GtkWidget* build_readouts_dock(App& app) {
|
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);
|
gtk_box_append(GTK_BOX(box), app.dock_measures_box);
|
||||||
|
|
||||||
app.dock_readouts = 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
|
// 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.
|
// first; otherwise the pages' own rebuild fills them.
|
||||||
dock_rebuild_cursors(app);
|
dock_rebuild_cursors(app);
|
||||||
@@ -147,22 +213,17 @@ void dock_rebuild_cursors(App& app) {
|
|||||||
child = next;
|
child = next;
|
||||||
}
|
}
|
||||||
app.dock_cursor_lines.clear();
|
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);
|
dock_cursors_header(grid);
|
||||||
int at = 1;
|
int at = 1;
|
||||||
for (const App::CursorLine& src : app.cursors_lines) {
|
for (const App::CursorLine& src : app.cursors_lines) {
|
||||||
App::DockCursorLine line;
|
App::DockCursorLine line;
|
||||||
line.label = fixed_label("", kNameChars, GTK_ALIGN_START);
|
line.label = fixed_label("", kNameChars, GTK_ALIGN_START);
|
||||||
if (src.label != nullptr) {
|
copy_who(line.label, src.label);
|
||||||
// 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)));
|
|
||||||
}
|
|
||||||
gtk_grid_attach(grid, line.label, 0, at, 1, 1);
|
gtk_grid_attach(grid, line.label, 0, at, 1, 1);
|
||||||
GtkWidget** cells[3] = {&line.a, &line.b, &line.d};
|
GtkWidget** cells[3] = {&line.a, &line.b, &line.d};
|
||||||
for (int c = 0; c < 3; ++c) {
|
for (int c = 0; c < 3; ++c) {
|
||||||
@@ -173,25 +234,41 @@ void dock_rebuild_cursors(App& app) {
|
|||||||
*cells[c] = v;
|
*cells[c] = v;
|
||||||
}
|
}
|
||||||
app.dock_cursor_lines.push_back(line);
|
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;
|
++at;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dock_rebuild_measures(App& app) {
|
void dock_rebuild_measures(App& app) {
|
||||||
if (app.dock_measures_box == nullptr) return;
|
if (app.dock_measures_box == nullptr) return;
|
||||||
GtkWidget* child = gtk_widget_get_first_child(app.dock_measures_box);
|
clear_box(app.dock_measures_box);
|
||||||
while (child != nullptr) {
|
if (app.strip_measure_box != nullptr) clear_box(app.strip_measure_box);
|
||||||
GtkWidget* next = gtk_widget_get_next_sibling(child);
|
|
||||||
gtk_box_remove(GTK_BOX(app.dock_measures_box), child);
|
|
||||||
child = next;
|
|
||||||
}
|
|
||||||
app.dock_measure_labels.clear();
|
app.dock_measure_labels.clear();
|
||||||
|
app.strip_measure_labels.clear();
|
||||||
if (app.measures.empty()) {
|
if (app.measures.empty()) {
|
||||||
GtkWidget* none = gtk_label_new("no measurements");
|
GtkWidget* none = gtk_label_new("no measurements");
|
||||||
gtk_widget_set_halign(none, GTK_ALIGN_START);
|
gtk_widget_set_halign(none, GTK_ALIGN_START);
|
||||||
gtk_widget_add_css_class(none, "caption");
|
gtk_widget_add_css_class(none, "caption");
|
||||||
gtk_widget_add_css_class(none, "dim-label");
|
gtk_widget_add_css_class(none, "dim-label");
|
||||||
gtk_box_append(GTK_BOX(app.dock_measures_box), none);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < app.measures.size(); ++i) {
|
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_widget_add_controller(line, GTK_EVENT_CONTROLLER(click));
|
||||||
gtk_box_append(GTK_BOX(app.dock_measures_box), line);
|
gtk_box_append(GTK_BOX(app.dock_measures_box), line);
|
||||||
app.dock_measure_labels.push_back(value);
|
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.a, a);
|
||||||
set_value_text(l.b, b);
|
set_value_text(l.b, b);
|
||||||
set_value_text(l.d, d);
|
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) {
|
void dock_measure_value(App& app, std::size_t i, const std::string& text) {
|
||||||
if (i >= app.dock_measure_labels.size()) return; // mid-rebuild
|
if (i >= app.dock_measure_labels.size()) return; // mid-rebuild
|
||||||
set_value_text(app.dock_measure_labels[i], text);
|
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 {
|
namespace {
|
||||||
|
|||||||
@@ -4337,6 +4337,90 @@ static int dock_width_fits(App& a, int* dock_w, int* side_w) {
|
|||||||
return *dock_w >= 0 && *side_w > 0 && *dock_w <= *side_w ? 1 : 0;
|
return *dock_w >= 0 && *side_w > 0 && *dock_w <= *side_w ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The readouts strip against the dock it mirrors: `same_text=1` when every strip label's text equals the
|
||||||
|
/// dock's (the cursors' `who` and delta cells, the measurements' values) and the counts agree. `visible` must
|
||||||
|
/// be the opposite of `dock_shown` (`show-sidebar`), and `h` the strip's fixed 26 whenever it is visible.
|
||||||
|
/// `sens_same` is the strip's cursor chips sharing the dock grid's sensitivity.
|
||||||
|
static void strip_report(App& a, const char* tag, const char* what) {
|
||||||
|
const auto text = [](GtkWidget* w) { return std::string(gtk_label_get_text(GTK_LABEL(w))); };
|
||||||
|
int same = a.strip_cursor_deltas.size() == a.dock_cursor_lines.size() &&
|
||||||
|
a.strip_cursor_who.size() == a.dock_cursor_lines.size() &&
|
||||||
|
a.strip_measure_labels.size() == a.dock_measure_labels.size()
|
||||||
|
? 1 : 0;
|
||||||
|
for (std::size_t i = 0; same != 0 && i < a.dock_cursor_lines.size(); ++i) {
|
||||||
|
if (text(a.strip_cursor_deltas[i]) != text(a.dock_cursor_lines[i].d) ||
|
||||||
|
text(a.strip_cursor_who[i]) != text(a.dock_cursor_lines[i].label)) {
|
||||||
|
same = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; same != 0 && i < a.dock_measure_labels.size(); ++i) {
|
||||||
|
if (text(a.strip_measure_labels[i]) != text(a.dock_measure_labels[i])) same = 0;
|
||||||
|
}
|
||||||
|
const bool dock_shown =
|
||||||
|
a.side_view != nullptr &&
|
||||||
|
adw_overlay_split_view_get_show_sidebar(ADW_OVERLAY_SPLIT_VIEW(a.side_view)) != FALSE;
|
||||||
|
const bool visible = a.readouts_strip != nullptr && gtk_widget_get_visible(a.readouts_strip);
|
||||||
|
const bool collapsed =
|
||||||
|
a.side_view != nullptr &&
|
||||||
|
adw_overlay_split_view_get_collapsed(ADW_OVERLAY_SPLIT_VIEW(a.side_view)) != FALSE;
|
||||||
|
const bool sens_same =
|
||||||
|
a.strip_cursor_box != nullptr && a.dock_cursor_grid != nullptr &&
|
||||||
|
gtk_widget_get_sensitive(a.strip_cursor_box) == gtk_widget_get_sensitive(a.dock_cursor_grid);
|
||||||
|
std::fprintf(stderr,
|
||||||
|
"[%s-probe] %-14s strip: visible=%d dock_shown=%d cursors=%zu measures=%zu same_text=%d "
|
||||||
|
"h=%d sens_same=%d collapsed=%d window=%d\n",
|
||||||
|
tag, what, visible ? 1 : 0, dock_shown ? 1 : 0, a.strip_cursor_deltas.size(),
|
||||||
|
a.strip_measure_labels.size(), same, a.readouts_strip != nullptr
|
||||||
|
? gtk_widget_get_height(a.readouts_strip) : -1,
|
||||||
|
sens_same ? 1 : 0, collapsed ? 1 : 0,
|
||||||
|
a.window != nullptr ? gtk_widget_get_width(a.window) : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts the dock away through the wish the tick re-asserts and reads the strip with it hidden
|
||||||
|
/// (`<tag>-probe ... strip:`: visible must be 1, h 26, same_text 1), then restores the wish and reads it
|
||||||
|
/// again (visible must be 0). Under `xvfb-run` the window is 756 px wide, below `kSidebarRoom`, so the split
|
||||||
|
/// view is `collapsed` on every line: both legs are the collapsed case (the tick keeps a wished dock
|
||||||
|
/// overlaid, a hidden one off), and `collapsed=0` is not reachable from a probe -- a window with no
|
||||||
|
/// manager cannot be resized by `gtk_window_set_default_size()`.
|
||||||
|
struct StripDrive {
|
||||||
|
App* app;
|
||||||
|
const char* tag;
|
||||||
|
int step;
|
||||||
|
};
|
||||||
|
|
||||||
|
static gboolean strip_drive_step(gpointer user) {
|
||||||
|
auto* d = static_cast<StripDrive*>(user);
|
||||||
|
App& a = *d->app;
|
||||||
|
if (a.window == nullptr) {
|
||||||
|
delete d;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
switch (d->step) {
|
||||||
|
case 0:
|
||||||
|
a.sidebar_wanted = false;
|
||||||
|
update_side_visibility(a);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
strip_report(a, d->tag, "dock hidden");
|
||||||
|
a.sidebar_wanted = true;
|
||||||
|
update_side_visibility(a);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strip_report(a, d->tag, "dock restored");
|
||||||
|
std::fflush(stderr);
|
||||||
|
delete d;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
std::fflush(stderr);
|
||||||
|
++d->step;
|
||||||
|
g_timeout_add(600, strip_drive_step, d);
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void strip_drive(App& a, const char* tag) {
|
||||||
|
g_timeout_add(100, strip_drive_step, new StripDrive{&a, tag, 0});
|
||||||
|
}
|
||||||
|
|
||||||
static void dock_cursors_report(App& a, const char* what) {
|
static void dock_cursors_report(App& a, const char* what) {
|
||||||
int same = a.dock_cursor_lines.size() == a.cursors_lines.size() ? 1 : 0;
|
int same = a.dock_cursor_lines.size() == a.cursors_lines.size() ? 1 : 0;
|
||||||
for (std::size_t i = 0; same != 0 && i < a.cursors_lines.size(); ++i) {
|
for (std::size_t i = 0; same != 0 && i < a.cursors_lines.size(); ++i) {
|
||||||
@@ -4356,6 +4440,7 @@ static void dock_cursors_report(App& a, const char* what) {
|
|||||||
"side_w=%d fits=%d dock_sensitive=%d\n",
|
"side_w=%d fits=%d dock_sensitive=%d\n",
|
||||||
what, a.dock_cursor_lines.size(), a.cursors_lines.size(), same, dock_w, side_w, fits,
|
what, a.dock_cursor_lines.size(), a.cursors_lines.size(), same, dock_w, side_w, fits,
|
||||||
a.dock_cursor_grid != nullptr && gtk_widget_get_sensitive(a.dock_cursor_grid) ? 1 : 0);
|
a.dock_cursor_grid != nullptr && gtk_widget_get_sensitive(a.dock_cursor_grid) ? 1 : 0);
|
||||||
|
strip_report(a, "cursors", what);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dock_measures_report(App& a, const char* what) {
|
static void dock_measures_report(App& a, const char* what) {
|
||||||
@@ -4374,6 +4459,7 @@ static void dock_measures_report(App& a, const char* what) {
|
|||||||
"side_w=%d fits=%d\n",
|
"side_w=%d fits=%d\n",
|
||||||
what, a.dock_measure_labels.size(), a.measure_labels.size(), same, dock_w, side_w,
|
what, a.dock_measure_labels.size(), a.measure_labels.size(), same, dock_w, side_w,
|
||||||
fits);
|
fits);
|
||||||
|
strip_report(a, "measures", what);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cursors_probe_report(App& a, const char* what) {
|
void cursors_probe_report(App& a, const char* what) {
|
||||||
@@ -4646,6 +4732,7 @@ gboolean cursors_probe_step(gpointer user) {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
cursors_probe_report(*a, "after ruler drag");
|
cursors_probe_report(*a, "after ruler drag");
|
||||||
|
strip_drive(*a, "cursors"); // the dock put away and the window narrowed, the strip read at each
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
@@ -4758,6 +4845,7 @@ gboolean measures_probe_step(gpointer user) {
|
|||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
measure_dialog_report(*a, "final");
|
measure_dialog_report(*a, "final");
|
||||||
dock_measures_report(*a, "final");
|
dock_measures_report(*a, "final");
|
||||||
|
strip_drive(*a, "measures");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ void install_css() {
|
|||||||
".udpscope-badge { padding: 2px 12px; min-height: 0; border-radius: 14px; }\n"
|
".udpscope-badge { padding: 2px 12px; min-height: 0; border-radius: 14px; }\n"
|
||||||
".udpscope-badge-open { background-color: alpha(@accent_bg_color, 0.28); }\n"
|
".udpscope-badge-open { background-color: alpha(@accent_bg_color, 0.28); }\n"
|
||||||
".udpscope-badge-state { opacity: 0.7; }\n"
|
".udpscope-badge-state { opacity: 0.7; }\n"
|
||||||
|
// The readouts strip's two text buttons: Adwaita's 34 px button would be taller than the strip.
|
||||||
|
"button.udpscope-strip-button { min-height: 0; padding: 1px 8px; }\n"
|
||||||
// The contextual-help "(i)": a *mark*, not a button. Adwaita's menu button is a 34x34 control
|
// The contextual-help "(i)": a *mark*, not a button. Adwaita's menu button is a 34x34 control
|
||||||
// (a 24 px floor plus 5 px of padding) with a 16 px glyph, which beside a heading reads as a
|
// (a 24 px floor plus 5 px of padding) with a 16 px glyph, which beside a heading reads as a
|
||||||
// second toolbar button. Measured on libadwaita 1.10: the outer `menubutton` node *and* its
|
// second toolbar button. Measured on libadwaita 1.10: the outer `menubutton` node *and* its
|
||||||
|
|||||||
Reference in new issue
Block a user