added status line info:
This commit is contained in:
1 parent
ea560ed272
commit
3d4085898e
3 files changed
+204
-9
No files matched your search
@@ -120,6 +120,13 @@ Two lines of the self-check output are assertions in disguise:
|
|||||||
`64·⌈(10+len(header)+1)/64⌉ + rows·(16 + 4·cols)`, so a size that is not that is a
|
`64·⌈(10+len(header)+1)/64⌉ + rows·(16 + 4·cols)`, so a size that is not that is a
|
||||||
mispacked record. Read it back with `pd.DataFrame(np.load(path))` after changing
|
mispacked record. Read it back with `pd.DataFrame(np.load(path))` after changing
|
||||||
either writer.
|
either writer.
|
||||||
|
- `badge frames: lit=L dim=D idle=I` — the activity badge's phase per frame. Over a run
|
||||||
|
that acquired anything **both `lit` and `dim` must be non-zero**: that is what "it
|
||||||
|
blinks" means, and a badge stuck lit is indistinguishable from a working one in a
|
||||||
|
screenshot. One or two `idle` frames at the start are the frames before the socket
|
||||||
|
connected; a large `idle` is correct only when the run paused or disarmed (a
|
||||||
|
`UDPSCOPE_PROBE_DISARM_MS` run should show hundreds). The `mode ...` lines carry the
|
||||||
|
rest of the status line, so `rate` and `last event ... ago` are checked there.
|
||||||
|
|
||||||
The probes are the regression net — when changing UI or plot-host behaviour, extend
|
The probes are the regression net — when changing UI or plot-host behaviour, extend
|
||||||
the probe/dump in `src/ui.cpp` rather than adding a separate harness.
|
the probe/dump in `src/ui.cpp` rather than adding a separate harness.
|
||||||
@@ -233,6 +240,11 @@ far the largest file.
|
|||||||
- **Layout must not depend on content.** The y gutter is reserved at the widest label
|
- **Layout must not depend on content.** The y gutter is reserved at the widest label
|
||||||
`TickFormat::Si` can produce and only ever grows; the status label is ellipsized so
|
`TickFormat::Si` can produce and only ever grows; the status label is ellipsized so
|
||||||
its text cannot drive the paned divider. Both exist to stop per-frame relayout.
|
its text cannot drive the paned divider. Both exist to stop per-frame relayout.
|
||||||
|
The same rule is why the activity badge is one glyph in three colours rather than
|
||||||
|
three glyphs, why the `rate` field collapses to one figure or a range instead of a
|
||||||
|
per-channel list, and why `format_elapsed()` steps to a coarser unit instead of
|
||||||
|
letting the counter grow: each is a status field whose *width* would otherwise depend
|
||||||
|
on the stream, and the status row is directly above the plot.
|
||||||
The reservation goes on the **figure** (`Figure::set_min_y_gutter()`, added to the
|
The reservation goes on the **figure** (`Figure::set_min_y_gutter()`, added to the
|
||||||
plotter for this), not on the rows: `Figure::layout()` clears every row's
|
plotter for this), not on the rows: `Figure::layout()` clears every row's
|
||||||
`set_min_y_gutter()` to measure what its own labels need, so a floor written onto the
|
`set_min_y_gutter()` to measure what its own labels need, so a floor written onto the
|
||||||
@@ -271,6 +283,11 @@ far the largest file.
|
|||||||
- **The export is collected at click time, not in the file-dialog callback.** The live
|
- **The export is collected at click time, not in the file-dialog callback.** The live
|
||||||
view keeps scrolling while the chooser is up, so a late collection would write a
|
view keeps scrolling while the chooser is up, so a late collection would write a
|
||||||
window the user never saw. The writer is picked by the chosen path's extension.
|
window the user never saw. The writer is picked by the chosen path's extension.
|
||||||
|
- **The status line's per-second fields are measured once a second, not per frame.**
|
||||||
|
`App::stored_rate`/`declared_rate` and the label's tooltip are refreshed on the same
|
||||||
|
tick as the channel dump; the status line only formats what is already there. The
|
||||||
|
badge's markup is likewise rewritten on a change of phase (twice a second), never on
|
||||||
|
all sixty frames.
|
||||||
- **The header bar's buttons are icon-only**, so headless probes must reach them
|
- **The header bar's buttons are icon-only**, so headless probes must reach them
|
||||||
through the `App` pointers (`trigger_toggle`, `pause_button`, …), not via
|
through the `App` pointers (`trigger_toggle`, `pause_button`, …), not via
|
||||||
`find_labelled_button()`. The Arm/Disarm button in the trigger panel still has text.
|
`find_labelled_button()`. The Arm/Disarm button in the trigger panel still has text.
|
||||||
|
|||||||
@@ -231,6 +231,30 @@ later, but the line marks the edge, so the pre/post split can be read straight o
|
|||||||
the plot. It is not one of the user's cursors: it has its own colour, ignores the
|
the plot. It is not one of the user's cursors: it has its own colour, ignores the
|
||||||
cursor show/hide toggle, and is parked (invisible) when no event is held.
|
cursor show/hide toggle, and is parked (invisible) when no event is held.
|
||||||
|
|
||||||
|
**The status line.** It starts with an activity badge and then the mode, the
|
||||||
|
connection, the measured rate, the packet/frame/gap counters, what plot 1 holds and
|
||||||
|
the buffer's size and span. Three of those are worth calling out:
|
||||||
|
|
||||||
|
- **The badge blinks green at 1 Hz while the pipeline is actually running** — the
|
||||||
|
stream is connected, acquisition is not paused, and in trigger mode the trigger is
|
||||||
|
armed. Anything else leaves it a steady grey, with the reason on its tooltip
|
||||||
|
(`paused`, `trigger disarmed`, `waiting for the stream`). *Blinking* is the signal,
|
||||||
|
not the colour: a steady badge is a stopped pipeline, and that distinction survives
|
||||||
|
a screenshot. It is one glyph whose colour is all that changes, so blinking it
|
||||||
|
cannot change its width — a badge that grew and shrank twice a second would move the
|
||||||
|
label beside it and re-lay out the plot with it.
|
||||||
|
- **`rate 9.99 kS/s` is the rate the buffer actually stored over the last second**, not
|
||||||
|
the rate the CONFIG declared. The app does **no** decimation (see `plan_partition()`),
|
||||||
|
so the two normally agree and a gap between them is the datasource's real wire rate.
|
||||||
|
One figure when every channel agrees within a percent, `lo..hi` when they do not;
|
||||||
|
the per-channel breakdown, stored against declared, is on the line's tooltip. A
|
||||||
|
per-channel list in the line itself would make its width depend on the stream.
|
||||||
|
- **`last event 12 s ago`** (trigger mode only) ages the picture on screen, in seconds,
|
||||||
|
then minutes, then hours. A frozen view looks the same whether the last event was a
|
||||||
|
second or an hour ago, so without it there is no way to tell a working trigger from
|
||||||
|
a dead one. Measured on the wall clock, not the stream's: it is a question about how
|
||||||
|
long *you* have been waiting. Before the first latch it reads `no event yet`.
|
||||||
|
|
||||||
Switching the header bar's **trigger** button off returns to `[live]` and resumes
|
Switching the header bar's **trigger** button off returns to `[live]` and resumes
|
||||||
drawing the stream; the trigger panel carries the same action as a
|
drawing the stream; the trigger panel carries the same action as a
|
||||||
**Go live (trigger off)** button, so the way out is inside the panel that froze
|
**Go live (trigger off)** button, so the way out is inside the panel that froze
|
||||||
|
|||||||
+163
-9
@@ -57,6 +57,12 @@ struct App {
|
|||||||
plot::Plot placeholder_plot;
|
plot::Plot placeholder_plot;
|
||||||
|
|
||||||
GtkWidget* status_label = nullptr;
|
GtkWidget* status_label = nullptr;
|
||||||
|
/// The activity badge: one glyph whose *colour* is the whole signal, so that blinking
|
||||||
|
/// it cannot change its width and drive a relayout (see the status label's comment).
|
||||||
|
GtkWidget* status_badge = nullptr;
|
||||||
|
/// What the badge was last set to (`BadgePhase`), so the markup is rewritten on a
|
||||||
|
/// change of phase rather than on every one of the 60 frames a second.
|
||||||
|
int badge_phase = -1;
|
||||||
GtkWidget* window_drop = nullptr;
|
GtkWidget* window_drop = nullptr;
|
||||||
GtkWidget* trigger_toggle = nullptr;
|
GtkWidget* trigger_toggle = nullptr;
|
||||||
GtkWidget* trigger_revealer = nullptr;
|
GtkWidget* trigger_revealer = nullptr;
|
||||||
@@ -94,7 +100,19 @@ struct App {
|
|||||||
/// frame, and whether an event is held (which is what "frozen" means).
|
/// frame, and whether an event is held (which is what "frozen" means).
|
||||||
std::uint64_t seen_latches = 0;
|
std::uint64_t seen_latches = 0;
|
||||||
bool event_held = false;
|
bool event_held = false;
|
||||||
|
/// Monotonic time of the latch `seen_latches` counts, so the status line can age it.
|
||||||
|
/// Wall time, not stream time: "how long since the last event" is a question about the
|
||||||
|
/// user's wait, and a stream whose clock runs at any other speed would answer it wrong.
|
||||||
|
/// 0 until the first latch of the session.
|
||||||
|
gint64 last_latch_us = 0;
|
||||||
double window_choices_limit = -1.0; ///< the cap the dropdown was last built with
|
double window_choices_limit = -1.0; ///< the cap the dropdown was last built with
|
||||||
|
/// The rate each channel actually *stored* over the last second, and the declared rate
|
||||||
|
/// beside it. The app does no decimation, so the stored rate is the datasource's own
|
||||||
|
/// wire rate rather than anything the viewer chose -- which is why it is worth showing:
|
||||||
|
/// the declared rate is a claim and this is the receipt. Re-measured once a second (the
|
||||||
|
/// same tick as the channel dump), never per frame.
|
||||||
|
std::vector<double> stored_rate;
|
||||||
|
std::vector<double> declared_rate;
|
||||||
/// Self-check: how much the axes' left edge moves between frames.
|
/// Self-check: how much the axes' left edge moves between frames.
|
||||||
int dbg_axes_x_min = 1 << 20;
|
int dbg_axes_x_min = 1 << 20;
|
||||||
int dbg_axes_x_max = -(1 << 20);
|
int dbg_axes_x_max = -(1 << 20);
|
||||||
@@ -108,6 +126,10 @@ struct App {
|
|||||||
int dbg_blank_frames = 0;
|
int dbg_blank_frames = 0;
|
||||||
/// Frames the one area staged: a figure is one `::render` for every row.
|
/// Frames the one area staged: a figure is one `::render` for every row.
|
||||||
int dbg_frames_painted = 0;
|
int dbg_frames_painted = 0;
|
||||||
|
/// Frames the badge spent in each `BadgePhase`. Both `lit` and `dim` have to be non-zero
|
||||||
|
/// over a run that acquired anything -- that is what "it blinks" means, and a badge
|
||||||
|
/// stuck lit looks identical to a working one in a screenshot.
|
||||||
|
int dbg_badge_frames[3] = {0, 0, 0};
|
||||||
int dbg_reset_visible = -1;
|
int dbg_reset_visible = -1;
|
||||||
std::vector<std::uint64_t> last_stored;
|
std::vector<std::uint64_t> last_stored;
|
||||||
std::uint64_t dbg_latches_at_disarm = 0;
|
std::uint64_t dbg_latches_at_disarm = 0;
|
||||||
@@ -258,6 +280,61 @@ std::string format_window(double s) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What the activity badge is saying. `Lit`/`Dim` are the two halves of the blink, so
|
||||||
|
/// the badge is only *blinking* while the pipeline is actually running; a steady badge is
|
||||||
|
/// a stopped one, which is the state worth noticing on a glance.
|
||||||
|
enum class BadgePhase { Lit = 0, Dim = 1, Idle = 2 };
|
||||||
|
|
||||||
|
/// The badge is one glyph in one of three colours. It must stay the *same glyph*: the
|
||||||
|
/// status row sits above the plot, and a badge that changed width as it blinked would
|
||||||
|
/// move the label beside it and re-lay out the plot twice a second.
|
||||||
|
const char* badge_markup(BadgePhase phase) {
|
||||||
|
switch (phase) {
|
||||||
|
case BadgePhase::Lit:
|
||||||
|
return "<span foreground=\"#45E045\">\u25CF</span>";
|
||||||
|
case BadgePhase::Dim:
|
||||||
|
return "<span foreground=\"#1C5A1C\">\u25CF</span>";
|
||||||
|
case BadgePhase::Idle:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "<span foreground=\"#5A5A5A\">\u25CF</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `12 s`, `3m 07s`, `2h 14m` -- one unit coarser as soon as the finer one would need
|
||||||
|
/// three digits, so the field never grows past six characters and the status line's shape
|
||||||
|
/// does not depend on how long the wait has been.
|
||||||
|
std::string format_elapsed(double seconds) {
|
||||||
|
if (!(seconds >= 0.0)) return "--";
|
||||||
|
char buf[32];
|
||||||
|
if (seconds < 60.0) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.0f s", std::floor(seconds));
|
||||||
|
} else if (seconds < 3600.0) {
|
||||||
|
const int m = static_cast<int>(seconds) / 60;
|
||||||
|
std::snprintf(buf, sizeof(buf), "%dm %02ds", m, static_cast<int>(seconds) % 60);
|
||||||
|
} else {
|
||||||
|
const int h = static_cast<int>(seconds) / 3600;
|
||||||
|
std::snprintf(buf, sizeof(buf), "%dh %02dm", h, (static_cast<int>(seconds) / 60) % 60);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sample rate in the unit it reads shortest in. Rates here span six decades (a 1 Hz
|
||||||
|
/// housekeeping signal next to a 1 MS/s acquisition), so a fixed unit makes one of the
|
||||||
|
/// two unreadable.
|
||||||
|
std::string format_rate(double per_s) {
|
||||||
|
char buf[32];
|
||||||
|
if (!(per_s > 0.0)) {
|
||||||
|
return "0 S/s";
|
||||||
|
} else if (per_s >= 1.0e6) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.3g MS/s", per_s / 1.0e6);
|
||||||
|
} else if (per_s >= 1.0e3) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.3g kS/s", per_s / 1.0e3);
|
||||||
|
} else {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.3g S/s", per_s);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
/// A short-lived line in front of the status text: the outcome of an action that the
|
/// A short-lived line in front of the status text: the outcome of an action that the
|
||||||
/// picture does not show by itself (a drop that changed nothing, an export that wrote a
|
/// picture does not show by itself (a drop that changed nothing, an export that wrote a
|
||||||
/// file). `tick()` clears it when it expires.
|
/// file). `tick()` clears it when it expires.
|
||||||
@@ -1272,6 +1349,7 @@ gboolean tick(gpointer user) {
|
|||||||
if (latches != app->seen_latches) {
|
if (latches != app->seen_latches) {
|
||||||
app->seen_latches = latches;
|
app->seen_latches = latches;
|
||||||
app->event_held = true;
|
app->event_held = true;
|
||||||
|
app->last_latch_us = g_get_monotonic_time();
|
||||||
// The window is pinned to exactly what the snapshot captured, read off the
|
// The window is pinned to exactly what the snapshot captured, read off the
|
||||||
// snapshot itself. Not off `event_span`/`pretrigger_pct`: those say what was
|
// snapshot itself. Not off `event_span`/`pretrigger_pct`: those say what was
|
||||||
// *asked for*, in stream seconds, while how much the capture actually covers
|
// *asked for*, in stream seconds, while how much the capture actually covers
|
||||||
@@ -1401,6 +1479,10 @@ gboolean tick(gpointer user) {
|
|||||||
// wire rate -- this line is the receipt for that.
|
// wire rate -- this line is the receipt for that.
|
||||||
static gint64 next_channels = 0;
|
static gint64 next_channels = 0;
|
||||||
const gint64 now_c = g_get_monotonic_time();
|
const gint64 now_c = g_get_monotonic_time();
|
||||||
|
/// Filled only on a measuring tick, and applied to the status label's tooltip after the
|
||||||
|
/// model lock is dropped: the status line can only carry the aggregate rate, so the
|
||||||
|
/// per-channel breakdown goes where there is room for it.
|
||||||
|
std::string rate_tip;
|
||||||
if (app->last_stored.size() != model.channel_count()) {
|
if (app->last_stored.size() != model.channel_count()) {
|
||||||
// Seed new entries with the current count: a fresh entry must not be counted
|
// Seed new entries with the current count: a fresh entry must not be counted
|
||||||
// as a delta from zero.
|
// as a delta from zero.
|
||||||
@@ -1414,6 +1496,8 @@ gboolean tick(gpointer user) {
|
|||||||
next_channels = now_c + G_TIME_SPAN_SECOND;
|
next_channels = now_c + G_TIME_SPAN_SECOND;
|
||||||
app->last_channels_meas = now_c;
|
app->last_channels_meas = now_c;
|
||||||
std::lock_guard<std::mutex> lock(model.mutex());
|
std::lock_guard<std::mutex> lock(model.mutex());
|
||||||
|
app->stored_rate.assign(model.channel_count(), 0.0);
|
||||||
|
app->declared_rate.assign(model.channel_count(), 0.0);
|
||||||
for (std::size_t c = 0; c < model.channel_count(); ++c) {
|
for (std::size_t c = 0; c < model.channel_count(); ++c) {
|
||||||
const ChannelInfo& info = model.channels()[c];
|
const ChannelInfo& info = model.channels()[c];
|
||||||
const u64 stored = model.live().values(c).produced();
|
const u64 stored = model.live().values(c).produced();
|
||||||
@@ -1430,6 +1514,10 @@ gboolean tick(gpointer user) {
|
|||||||
stored >= prev && dt_s > 0.0 ? double(stored - prev) / dt_s : 0.0;
|
stored >= prev && dt_s > 0.0 ? double(stored - prev) / dt_s : 0.0;
|
||||||
if (c < app->last_stored.size()) app->last_stored[static_cast<std::size_t>(c)] = stored;
|
if (c < app->last_stored.size()) app->last_stored[static_cast<std::size_t>(c)] = stored;
|
||||||
const double declared = info.dt > 0.0 ? 1.0 / info.dt : 0.0;
|
const double declared = info.dt > 0.0 ? 1.0 / info.dt : 0.0;
|
||||||
|
app->stored_rate[c] = per_s;
|
||||||
|
app->declared_rate[c] = declared;
|
||||||
|
rate_tip += (rate_tip.empty() ? "" : "\n") + info.name + ": " + format_rate(per_s) +
|
||||||
|
" stored (declared " + format_rate(declared) + ")";
|
||||||
const ChannelBuffer& buf = model.live().channel(c);
|
const ChannelBuffer& buf = model.live().channel(c);
|
||||||
const plot::Range y = buf.size() > 0 ? buf.y_bounds() : plot::Range{0.0, 0.0};
|
const plot::Range y = buf.size() > 0 ? buf.y_bounds() : plot::Range{0.0, 0.0};
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
@@ -1496,6 +1584,30 @@ gboolean tick(gpointer user) {
|
|||||||
} else if (app->acq->paused()) {
|
} else if (app->acq->paused()) {
|
||||||
mode = "paused";
|
mode = "paused";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The badge: lit and blinking while the pipeline is actually running end to end.
|
||||||
|
// Paused and disarmed both stop it, and so does a stream that has not arrived -- a
|
||||||
|
// green light over a dead socket is the one reading that must not happen. The blink is
|
||||||
|
// a 1 Hz square wave off the monotonic clock rather than its own timer, so it cannot
|
||||||
|
// drift away from the frame it is drawn on.
|
||||||
|
const bool paused = app->acq->paused();
|
||||||
|
const bool active = st.connected.load() && !paused && (!trigger_on || armed);
|
||||||
|
const bool lit = ((g_get_monotonic_time() / (G_TIME_SPAN_SECOND / 2)) & 1) == 0;
|
||||||
|
const BadgePhase phase =
|
||||||
|
!active ? BadgePhase::Idle : (lit ? BadgePhase::Lit : BadgePhase::Dim);
|
||||||
|
const int reason = active ? 0 : !st.connected.load() ? 1 : paused ? 2 : 3;
|
||||||
|
static const char* const kWhy[] = {"acquiring", "waiting for the stream", "paused",
|
||||||
|
"trigger disarmed"};
|
||||||
|
// Keyed on the reason as well as the phase: three of the reasons share the Idle phase,
|
||||||
|
// so a pause that becomes a disarm has to reach the tooltip.
|
||||||
|
++app->dbg_badge_frames[static_cast<int>(phase)];
|
||||||
|
const int badge_key = static_cast<int>(phase) * 8 + reason;
|
||||||
|
if (app->status_badge != nullptr && badge_key != app->badge_phase) {
|
||||||
|
app->badge_phase = badge_key;
|
||||||
|
gtk_label_set_markup(GTK_LABEL(app->status_badge), badge_markup(phase));
|
||||||
|
gtk_widget_set_tooltip_text(app->status_badge, kWhy[reason]);
|
||||||
|
}
|
||||||
|
|
||||||
int bound = 0;
|
int bound = 0;
|
||||||
std::size_t samples = 0;
|
std::size_t samples = 0;
|
||||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||||
@@ -1505,18 +1617,46 @@ gboolean tick(gpointer user) {
|
|||||||
samples += static_cast<std::size_t>(b->size());
|
samples += static_cast<std::size_t>(b->size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
char status[768];
|
// The rate the buffer is actually storing at. One figure when every channel agrees and
|
||||||
|
// a range when they do not: a per-channel list would make the field's width depend on
|
||||||
|
// the stream, and the breakdown is on the label's tooltip instead.
|
||||||
|
std::string rate = "rate --";
|
||||||
|
if (!app->stored_rate.empty()) {
|
||||||
|
double lo = app->stored_rate.front();
|
||||||
|
double hi = lo;
|
||||||
|
for (const double r : app->stored_rate) {
|
||||||
|
lo = std::min(lo, r);
|
||||||
|
hi = std::max(hi, r);
|
||||||
|
}
|
||||||
|
// Within a percent is the same rate measured over a one-second window, not two rates.
|
||||||
|
rate = "rate " + (hi - lo <= 0.01 * hi ? format_rate(hi)
|
||||||
|
: format_rate(lo) + ".." + format_rate(hi));
|
||||||
|
}
|
||||||
|
|
||||||
|
// In trigger mode, how long ago the event on screen was captured. It is what tells a
|
||||||
|
// frozen picture apart from a stale one: the view looks identical whether the last
|
||||||
|
// event was a second or an hour ago.
|
||||||
|
std::string since;
|
||||||
|
if (trigger_on) {
|
||||||
|
since = app->last_latch_us != 0
|
||||||
|
? " | last event " +
|
||||||
|
format_elapsed(double(g_get_monotonic_time() - app->last_latch_us) / 1.0e6) +
|
||||||
|
" ago"
|
||||||
|
: " | no event yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
char status[1024];
|
||||||
std::snprintf(status, sizeof(status),
|
std::snprintf(status, sizeof(status),
|
||||||
"[%s] %s %s | packets %llu frames %llu gaps %llu | %zu signals, %zu "
|
"[%s] %s %s | %s | packets %llu frames %llu gaps %llu | %zu signals, %zu "
|
||||||
"channels | plot 1: %d trace(s), %zu samples | buffer %.1f MB, %.2f s span, "
|
"channels | plot 1: %d trace(s), %zu samples | buffer %.1f MB, %.2f s span, "
|
||||||
"time x%llu",
|
"time x%llu%s",
|
||||||
mode, st.connected.load() ? "connected" : "waiting",
|
mode, st.connected.load() ? "connected" : "waiting",
|
||||||
st.have_config.load() ? "configured" : "-",
|
st.have_config.load() ? "configured" : "-", rate.c_str(),
|
||||||
static_cast<unsigned long long>(st.packets.load()),
|
static_cast<unsigned long long>(st.packets.load()),
|
||||||
static_cast<unsigned long long>(st.frames.load()),
|
static_cast<unsigned long long>(st.frames.load()),
|
||||||
static_cast<unsigned long long>(st.gaps.load()), signal_count, channel_count, bound,
|
static_cast<unsigned long long>(st.gaps.load()), signal_count, channel_count, bound,
|
||||||
samples, double(model.config().buffer_mb), span_s,
|
samples, double(model.config().buffer_mb), span_s,
|
||||||
static_cast<unsigned long long>(time_stride));
|
static_cast<unsigned long long>(time_stride), since.c_str());
|
||||||
if (!app->notice.empty() && g_get_monotonic_time() < app->notice_until_us) {
|
if (!app->notice.empty() && g_get_monotonic_time() < app->notice_until_us) {
|
||||||
const std::string with_notice = app->notice + " | " + status;
|
const std::string with_notice = app->notice + " | " + status;
|
||||||
gtk_label_set_text(GTK_LABEL(app->status_label), with_notice.c_str());
|
gtk_label_set_text(GTK_LABEL(app->status_label), with_notice.c_str());
|
||||||
@@ -1524,6 +1664,7 @@ gboolean tick(gpointer user) {
|
|||||||
app->notice.clear();
|
app->notice.clear();
|
||||||
gtk_label_set_text(GTK_LABEL(app->status_label), status);
|
gtk_label_set_text(GTK_LABEL(app->status_label), status);
|
||||||
}
|
}
|
||||||
|
if (!rate_tip.empty()) gtk_widget_set_tooltip_text(app->status_label, rate_tip.c_str());
|
||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1737,6 +1878,16 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
gtk_box_append(GTK_BOX(left), app->plot_box);
|
gtk_box_append(GTK_BOX(left), app->plot_box);
|
||||||
gtk_widget_set_vexpand(app->plot_box, true);
|
gtk_widget_set_vexpand(app->plot_box, true);
|
||||||
|
|
||||||
|
// The badge and the text share one row so the badge sits at the line's head. It is a
|
||||||
|
// sibling rather than part of the label's own markup because the label's text is
|
||||||
|
// rewritten every frame and its markup is ellipsized -- a blinking span inside it would
|
||||||
|
// be reparsed sixty times a second and could be the character that gets elided.
|
||||||
|
GtkWidget* status_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||||
|
app->status_badge = gtk_label_new(nullptr);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(app->status_badge), badge_markup(BadgePhase::Idle));
|
||||||
|
gtk_widget_set_tooltip_text(app->status_badge, "waiting for the stream");
|
||||||
|
gtk_box_append(GTK_BOX(status_row), app->status_badge);
|
||||||
|
|
||||||
app->status_label = gtk_label_new("starting...");
|
app->status_label = gtk_label_new("starting...");
|
||||||
// The line is rewritten every frame and its text changes width as the counters
|
// The line is rewritten every frame and its text changes width as the counters
|
||||||
// grow. A label's *minimum* width is its text, so an unellipsized one pushes the
|
// grow. A label's *minimum* width is its text, so an unellipsized one pushes the
|
||||||
@@ -1746,10 +1897,11 @@ void on_activate(GtkApplication* application, gpointer user) {
|
|||||||
gtk_label_set_ellipsize(GTK_LABEL(app->status_label), PANGO_ELLIPSIZE_END);
|
gtk_label_set_ellipsize(GTK_LABEL(app->status_label), PANGO_ELLIPSIZE_END);
|
||||||
gtk_widget_set_hexpand(app->status_label, true);
|
gtk_widget_set_hexpand(app->status_label, true);
|
||||||
gtk_widget_set_halign(app->status_label, GTK_ALIGN_FILL);
|
gtk_widget_set_halign(app->status_label, GTK_ALIGN_FILL);
|
||||||
gtk_widget_set_margin_top(app->status_label, 2);
|
gtk_box_append(GTK_BOX(status_row), app->status_label);
|
||||||
gtk_widget_set_margin_bottom(app->status_label, 2);
|
gtk_widget_set_margin_top(status_row, 2);
|
||||||
gtk_widget_set_margin_start(app->status_label, 6);
|
gtk_widget_set_margin_bottom(status_row, 2);
|
||||||
gtk_box_append(GTK_BOX(left), app->status_label);
|
gtk_widget_set_margin_start(status_row, 6);
|
||||||
|
gtk_box_append(GTK_BOX(left), status_row);
|
||||||
|
|
||||||
// The right-hand side: two independently collapsible panels, each revealed by
|
// The right-hand side: two independently collapsible panels, each revealed by
|
||||||
// its toolbar toggle. No outer scrolled window -- the signal list scrolls
|
// its toolbar toggle. No outer scrolled window -- the signal list scrolls
|
||||||
@@ -2271,6 +2423,8 @@ int run_ui(int argc, char** argv, ScopeModel& model, Acquisition& acq, RowSet& r
|
|||||||
app.dbg_marker_x, app.dbg_marker_max, app.dbg_trigger_time,
|
app.dbg_marker_x, app.dbg_marker_max, app.dbg_trigger_time,
|
||||||
app.dbg_trigger_time_valid);
|
app.dbg_trigger_time_valid);
|
||||||
std::printf("[udpscope] mode after go-live: %s\n", app.dbg_mode_live.c_str());
|
std::printf("[udpscope] mode after go-live: %s\n", app.dbg_mode_live.c_str());
|
||||||
|
std::printf("[udpscope] badge frames: lit=%d dim=%d idle=%d\n", app.dbg_badge_frames[0],
|
||||||
|
app.dbg_badge_frames[1], app.dbg_badge_frames[2]);
|
||||||
std::printf("[udpscope] trigger panel revealed before the probe toggled it: %d; "
|
std::printf("[udpscope] trigger panel revealed before the probe toggled it: %d; "
|
||||||
"signal list items=%d selected=%d; column visible after sidebar "
|
"signal list items=%d selected=%d; column visible after sidebar "
|
||||||
"toggle off=%d\n",
|
"toggle off=%d\n",
|
||||||
|
|||||||
Reference in new issue
Block a user