Fifteen display strings in the two preference forms were written as GTK mnemonics -- `_Directory`, `_Server`, `Multicast _group`, `_Theme` and the rest -- but the property that makes GTK read them was never set, so GTK drew the underscore literally: `_Directory` in the row, not a D with a line under it. Two switches are involved, FALSE by default in each case. `GtkLabel`/`GtkButton` take `use-underline`; the rows take `AdwPreferencesRow:use-underline`, "whether an embedded underline in the title indicates a mnemonic". The two buttons in the source form already set it (so their mnemonics worked and showed no underscore); the thirteen rows did not. All thirteen now do. The row classes that carry the property are not all related -- AdwEntryRow has its own, while AdwSpinRow, AdwComboRow and AdwSwitchRow get it through AdwActionRow -- so the probe reads it by name on a GObject (g_object_class_find_property, then g_object_get) rather than casting to one row type; a cast to a `GtkWidget*` where the type is only forward-declared does not compile at all. `_Cancel` and `_Connect` both claimed C in the same form, so Alt+C could only reach one of them: that marker was unreachable, which is worse than absent because it is in the source to be trusted. `_Connect` becomes `Conn_ect`, freeing C for Cancel. [prefs-probe] reads the count (4/4) on the real widgets; check_ui validates the property against the GIR for every object in every .ui file, inherited properties included.
1371 lines
70 KiB
C++
1371 lines
70 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
//
|
|
// The front end's *internal* interface.
|
|
//
|
|
// `ui.cpp` was one 13-thousand-line translation unit. The window is assembled from several, and this
|
|
// header is what they share: the application state (`App`) and the free functions the front end is
|
|
// built from. Nothing here is part of the app's public surface -- `ui.hpp` is -- and nothing here owns
|
|
// sample data: the model does, and `RowSet` owns the figure.
|
|
//
|
|
// The *definitions* carry the default arguments (they were written that way), so these declarations
|
|
// are generated without them: a default in both places is an error.
|
|
|
|
#pragma once
|
|
|
|
|
|
#include <gtk4/gtk_plot_area.hpp>
|
|
|
|
#include "acquisition.hpp"
|
|
#include "build_info.hpp"
|
|
#include "capture.hpp"
|
|
#include "config.hpp"
|
|
#include "event_store.hpp"
|
|
#include "export.hpp"
|
|
#include "help_window.hpp"
|
|
#include "layout_store.hpp"
|
|
#include "measure.hpp"
|
|
#include "model.hpp"
|
|
#include "plot_host.hpp"
|
|
#include "search.hpp"
|
|
#include "settings.hpp"
|
|
#include "ui_prefs.hpp"
|
|
#include "shortcuts.hpp"
|
|
#include "status_text.hpp"
|
|
#include "waveform.hpp"
|
|
|
|
|
|
|
|
#include <adwaita.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace udpscope {
|
|
namespace ui_impl {
|
|
|
|
enum class EventGroupKind {
|
|
None, ///< nothing: streaming, one event replayed, or a file opened
|
|
Compare, ///< two events in two plots side by side, at one scale, with synchronised axes
|
|
Accumulate, ///< up to four events overlaid in the same traces, translucent, so agreement is brightness
|
|
};
|
|
struct App {
|
|
ScopeModel* model = nullptr;
|
|
Acquisition* acq = nullptr;
|
|
RowSet* rows = nullptr;
|
|
|
|
GtkWidget* window = nullptr;
|
|
GtkWidget* plot_box = nullptr;
|
|
/// The one GL area, drawing the `RowSet`'s figure: every row is a row of that
|
|
/// figure, so there is one widget, one context and one `::render` whatever the row
|
|
/// count is.
|
|
GtkWidget* area_widget = nullptr;
|
|
plot::gtk4::PlotArea* area = nullptr;
|
|
/// The GL area wrapped in a `GtkOverlay`, which is what the plot box actually holds.
|
|
/// The layout editor's buttons are overlay children: each is a small widget placed by
|
|
/// its margins over the row it acts on, so it intercepts pointer events only where it
|
|
/// is -- a single transparent child covering the area would swallow every pan, drop and
|
|
/// hover the editor is not interested in.
|
|
GtkWidget* area_overlay = nullptr;
|
|
/// On while the layout editor is up. Nothing else about the plot changes: the editor is
|
|
/// a set of overlay buttons, not a mode the figure knows about.
|
|
bool layout_editing = false;
|
|
GtkWidget* layout_toggle = nullptr;
|
|
/// The editor's buttons, in the order they were added, so they can be taken off the
|
|
/// overlay again. Rebuilt when the row count changes, repositioned when the rows move.
|
|
std::vector<GtkWidget*> layout_buttons;
|
|
/// The boundary a resize drag is on, and the weights it started from: a drag is *relative* to where it
|
|
/// began, so the beginning has to be kept -- reading the weights while dragging would compound.
|
|
int layout_drag_row = -1;
|
|
std::vector<float> layout_drag_start;
|
|
/// The editor's way out: one `Done` button in the plot area's top-right corner while
|
|
/// the editor is up. Not part of `layout_buttons`, which is one button per row and per
|
|
/// boundary -- the count the layout probe asserts.
|
|
GtkWidget* layout_done_button = nullptr;
|
|
/// The row count and figure geometry the buttons were placed for. Their positions are
|
|
/// restated only when one of these moves: a `gtk_widget_set_margin_top()` per frame is a
|
|
/// relayout per frame, and overlay children are measured out of the main child's
|
|
/// allocation, so the churn would reach the plot.
|
|
int layout_buttons_rows = 0;
|
|
/// Which step of the layout probe runs next (`UDPSCOPE_PROBE_LAYOUT_MS`). The steps are
|
|
/// a frame apart, because the buttons are placed from the rects the last frame was laid
|
|
/// out with, so the sequence cannot be one function.
|
|
int dbg_layout_step = 0;
|
|
/// Which step of the cursors probe runs next (`UDPSCOPE_PROBE_CURSORS_MS`).
|
|
int dbg_cursors_step = 0;
|
|
/// Which step of the fit probe runs next (`UDPSCOPE_PROBE_FIT_MS`).
|
|
int dbg_fit_step = 0;
|
|
/// Which step of the x-label-form probe runs next (`UDPSCOPE_PROBE_TIMELABELS_MS`).
|
|
int dbg_timelabels_step = 0;
|
|
/// The cursors probe's own bookkeeping: the stored time before a gestureless move, so the
|
|
/// report can assert the move was ignored.
|
|
double dbg_gestureless_before = 0.0;
|
|
/// What `PlotArea::create()` is handed. The area is in figure mode from the start and
|
|
/// never draws this, but it keeps a reference to the plot it was created with (its
|
|
/// single-plot controller holds one) -- and every plot of the figure is destroyed by a
|
|
/// rebuild, so handing it a row would leave that reference dangling.
|
|
plot::Plot placeholder_plot;
|
|
|
|
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;
|
|
/// The display-window control: a header-bar button whose label is the current window
|
|
/// and whose popover holds the number, its unit and the two decade steppers. The label
|
|
/// is width-locked (see `kWindowLabelChars`) because the header bar sits above the plot
|
|
/// and a label that grew with the text would relayout everything under it.
|
|
GtkWidget* window_button = nullptr;
|
|
/// The span the control shows, pinned to `kWindowLabelChars` and ellipsized: the bar sits above the
|
|
/// plot, so a label that grew with its text would relayout everything under it on a change of window.
|
|
GtkWidget* window_label = nullptr;
|
|
GtkWidget* window_entry = nullptr;
|
|
GtkWidget* window_unit_drop = nullptr;
|
|
/// The unit `sync_window_display()` last wrote into the dropdown. `GtkDropDown`'s
|
|
/// `notify::selected` arrives *after* the call that caused it returns, so the
|
|
/// `updating_panels` flag is already clear by then and cannot tell our own write from the
|
|
/// user's pick; the entry the sync wrote is rounded for reading, so re-parsing it made the
|
|
/// rounding the window (a 500 s request came back as 499.98 s). A selection that matches
|
|
/// what we synced is ours -- GTK only notifies on a change, so the user cannot produce it.
|
|
int window_sync_unit = -1;
|
|
GtkWidget* trigger_toggle = nullptr;
|
|
GtkWidget* trigger_revealer = nullptr;
|
|
/// The trigger panel's own box, kept so the defaults probe can read its spin buttons without
|
|
/// walking the window for them.
|
|
GtkWidget* trigger_panel = nullptr;
|
|
GtkWidget* signal_toggle = nullptr;
|
|
GtkWidget* signal_revealer = nullptr;
|
|
GtkWidget* signal_panel = nullptr;
|
|
GtkWidget* side_box = nullptr;
|
|
GtkWidget* arm_button = nullptr;
|
|
GtkWidget* trigger_signal_drop = nullptr;
|
|
GtkWidget* sidebar_toggle = nullptr;
|
|
GtkWidget* side_scroll = nullptr;
|
|
GtkWidget* trigger_edge_drop = nullptr;
|
|
GtkWidget* trigger_type_drop = nullptr;
|
|
GtkWidget* trigger_cmp_drop = nullptr;
|
|
GtkWidget* trigger_width_spin = nullptr;
|
|
GtkWidget* trigger_width2_spin = nullptr;
|
|
GtkWidget* trigger_level2_spin = nullptr;
|
|
GtkWidget* trigger_timeout_spin = nullptr;
|
|
/// The `labeled_box` rows a condition may not use: held so `sync_trigger_rows()` can
|
|
/// hide them. A hidden row keeps its widget's value, so switching condition and back
|
|
/// does not lose what was typed.
|
|
GtkWidget* trigger_cmp_row = nullptr;
|
|
GtkWidget* trigger_width_row = nullptr;
|
|
GtkWidget* trigger_width2_row = nullptr;
|
|
GtkWidget* trigger_level2_row = nullptr;
|
|
GtkWidget* trigger_timeout_row = nullptr;
|
|
GtkWidget* trigger_mode_drop = nullptr;
|
|
GtkWidget* trigger_level_spin = nullptr;
|
|
GtkWidget* trigger_pre_spin = nullptr;
|
|
GtkWidget* trigger_holdoff_spin = nullptr;
|
|
GtkWidget* signal_list = nullptr;
|
|
GtkWidget* pause_button = nullptr;
|
|
/// When the pause began, on the **monotonic wall clock**, or 0 while running. The elapsed time
|
|
/// the status bar shows has to be wall time rather than stream time: the producer never stops
|
|
/// while paused (it keeps filling the live ring, which is what makes the resume seamless), so
|
|
/// the stream's own clock advances whether the view is frozen or not and would say nothing
|
|
/// about how long the user has been looking at a still picture.
|
|
gint64 pause_started_us = 0;
|
|
/// The buffer search. Driven a few milliseconds at a time from the frame tick, because its
|
|
/// samples live behind the model's mutex and the producer needs it back between chunks.
|
|
BufferSearch search;
|
|
GtkWidget* search_run_button = nullptr;
|
|
GtkWidget* search_cancel_button = nullptr;
|
|
GtkWidget* search_status_label = nullptr;
|
|
GtkWidget* search_pos_label = nullptr;
|
|
/// "16 of 38 marks shown" -- the pool is smaller than a scan's result, and a display that is
|
|
/// showing a sixth of what it found has to say so.
|
|
GtkWidget* search_marks_label = nullptr;
|
|
GtkWidget* search_prev_button = nullptr;
|
|
GtkWidget* search_next_button = nullptr;
|
|
/// Set true when the last `search_jump()` produced an empty cut — the hit was so close to the
|
|
/// oldest retained sample that `publish_pause_view()` held nothing. Cleared at every jump.
|
|
bool search_jump_empty = false;
|
|
GtkWidget* menu_button = nullptr;
|
|
GtkWidget* cursors_toggle = nullptr;
|
|
/// Where the panel controls ended up, decided once at build time: the dump runs after the window
|
|
/// is destroyed and cannot ask GTK anything about a widget.
|
|
std::string dbg_panel_controls;
|
|
/// The icon each toolbar button carries, and whether the theme resolved it. Recorded at the
|
|
/// same moment and for the same reason, but it guards a different thing: four of the icons are
|
|
/// drawn for this app and shipped inside the binary, and a resource path that never got
|
|
/// registered is *silent* -- GTK substitutes a missing-image placeholder, the button still
|
|
/// works, and every other line of the dump still passes.
|
|
std::string dbg_toolbar_icons;
|
|
std::string dbg_app_icons;
|
|
/// The header bar, so the dump can report the order the controls are in: a header bar is a
|
|
/// *titlebar*, not a child of the window, so walking down from the window does not reach it.
|
|
GtkWidget* toolbar = nullptr;
|
|
GtkWidget* cursors_reset = nullptr;
|
|
/// The cursors pane: revealed with the toggle, and listing what the instruments
|
|
/// read -- T0/T1/dT always, per row an Y1/Y2/dY where rulers are up, and in `mix` mode
|
|
/// the value each cursor crosses each trace at.
|
|
GtkWidget* cursors_revealer = nullptr;
|
|
GtkWidget* cursors_mode_drop = nullptr;
|
|
GtkWidget* cursors_body = nullptr;
|
|
/// One line per row (rulers) and one per bound trace (`mix`), in the order the body
|
|
/// was built with. The texts are rewritten each frame; the widgets are rebuilt only
|
|
/// when the rows or the mode change.
|
|
/// One row of the pane's table: a `who` cell and the three value cells, so the numbers
|
|
/// line up as columns the way a table does.
|
|
struct CursorLine {
|
|
GtkWidget* label = nullptr; ///< the `who` cell (a chip's markup, or text)
|
|
GtkWidget* a = nullptr;
|
|
GtkWidget* b = nullptr;
|
|
GtkWidget* d = nullptr;
|
|
int row = -1;
|
|
int slot = -1;
|
|
};
|
|
std::vector<CursorLine> cursors_lines;
|
|
/// 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
|
|
/// window scrolls every frame -- 60 recomputations a second of every sample is not a
|
|
/// price the frame tick should pay).
|
|
std::vector<Measure> measures;
|
|
std::vector<MeasureResult> measure_results;
|
|
GtkWidget* measures_revealer = nullptr;
|
|
GtkWidget* measures_list = nullptr;
|
|
GtkWidget* measures_toggle = nullptr;
|
|
// --- math channels --------------------------------------------------------
|
|
/// The app's copy of the math channels, and the source of truth for the pane. Every change goes
|
|
/// through `apply_math_channels()`, which writes this and hands it to the model -- the model's own
|
|
/// copy is what the producer evaluates.
|
|
std::vector<MathChannel> math;
|
|
GtkWidget* math_toggle = nullptr;
|
|
GtkWidget* math_revealer = nullptr;
|
|
GtkWidget* math_list = nullptr;
|
|
GtkWidget* math_dialog = nullptr;
|
|
/// One value label per measure, in `measures` order; rebuilt with the list.
|
|
std::vector<GtkWidget*> measure_labels;
|
|
/// The text each label was last set to. The dump runs from the destroy handler, where the
|
|
/// labels are gone, so it prints these instead of reading freed widgets.
|
|
std::vector<std::string> measure_text;
|
|
/// The window the current results were computed over, and whether a capture was held then:
|
|
/// a recomputation is owed when either moves (see `update_measures`).
|
|
///
|
|
/// It is kept in **stream** seconds (through `ScopeModel::stream_at_x0()`), not in the display x
|
|
/// it was read as, because a live view is newest-relative: the axis there is a fixed
|
|
/// `[-window, 0]` whatever arrives, so "the window moved" is invisible in display coordinates
|
|
/// and the pane would compute once and then never again -- the readout frozen on the samples
|
|
/// that happened to be on screen when the pane opened.
|
|
plot::Range measure_window{};
|
|
bool measure_window_valid = false;
|
|
bool measure_was_held = false;
|
|
/// What the two walks over one channel's visible window produced, shared by every measurement
|
|
/// on that signal: the window's statistics, and one accumulator per *distinct level* any of
|
|
/// them asked for. Several measurements on one signal must cost two walks, not two each -- and
|
|
/// the window itself is never stored, which is what the chunked walks bought (see
|
|
/// `MeasureChunk`).
|
|
struct MeasureWindow {
|
|
MeasureStats stats;
|
|
std::vector<double> pct; ///< the level shares asked for, deduplicated
|
|
std::vector<MeasureLevel> level; ///< one accumulator per entry of `pct`
|
|
bool ok = false;
|
|
};
|
|
std::unordered_map<int, MeasureWindow> measure_samples;
|
|
/// The window's LOD buckets, reused across recomputes: a 1.7 M-sample window is 26 k of them,
|
|
/// and both passes walk the same list.
|
|
std::vector<MeasureBucket> measure_bucket_scratch;
|
|
/// The ink probe's gather buffer, reused for the same reason: it is refilled on every
|
|
/// painted frame and its size is O(axis pixels).
|
|
std::vector<plot::f32> measure_ink_scratch;
|
|
/// When the pane was last recomputed, for the refresh cadence above.
|
|
gint64 measure_stamp_us = 0;
|
|
bool measure_stamp_valid = false;
|
|
/// What the pane cost: the recomputes it ran and the wall time they took. A readout that is
|
|
/// recomputed per frame over a live window is exactly the kind of thing that is invisible in a
|
|
/// frame mean, so it is measured where it happens and reported in the dump.
|
|
gint64 measure_cost_us = 0;
|
|
int measure_passes = 0;
|
|
/// How much of the window the pane actually *read*: the samples covered, against the LOD
|
|
/// buckets the two walks answered from and the samples they still had to open. That ratio is
|
|
/// the whole question for this pane -- a bucket is 64 samples not read, and the raw tally is
|
|
/// the window's partial edges plus the buckets that straddle a level, which on a clean
|
|
/// waveform is one per crossing. A `raw` near `covered` means the pyramid bought nothing
|
|
/// (a noisy signal, or a window shorter than a bucket) and the cost is back to O(window).
|
|
gint64 measure_fetch_us = 0;
|
|
std::uint64_t measure_covered = 0;
|
|
std::uint64_t measure_bucketed = 0;
|
|
std::uint64_t measure_raw = 0;
|
|
/// The last pass' own duration, which the cadence adapts to.
|
|
gint64 measure_last_pass_us = 0;
|
|
/// The last *valid* result per measurement, which the marks fall back to: a level is a property
|
|
/// of the signal and survives the window it was measured in, so the rulers do not blink off when
|
|
/// a pass lands on a window with no samples (a rebuild, a capture boundary, a mode change).
|
|
std::vector<MeasureResult> measure_last_valid;
|
|
/// Running statistics, one per entry of `measures` and reset whenever that vector is
|
|
/// rebuilt. Not reset by a pause, a latch or a pan: those change what is measured *now*,
|
|
/// not what the measurement means, and a statistic the user cannot keep across them is
|
|
/// not worth having.
|
|
std::vector<MeasureStatsAccum> measure_stats;
|
|
/// The second line of each measurement's row, one per entry of `measures`. Hidden rather
|
|
/// than removed when the toggle is off, so turning it on costs no rebuild -- and a rebuild
|
|
/// is what would reset the statistics the user just asked to see.
|
|
std::vector<GtkWidget*> measure_stats_labels;
|
|
bool measure_stats_on = false;
|
|
GtkWidget* measures_stats_check = nullptr;
|
|
GtkWidget* measures_stats_reset = nullptr;
|
|
/// The marks the last computation produced: re-placed every frame, which is what makes the
|
|
/// measurement cursors read-only.
|
|
///
|
|
/// A **time** mark's `value` is held in stream seconds, like `measure_window`, and converted to
|
|
/// the axis' own x by `place_measure_marks()` on the frame it is placed. That is what keeps a
|
|
/// mark glued to the sample it measured while a live view rolls the data leftwards under it --
|
|
/// a mark is a *result*, so it travels with its datum, which is the opposite of a cursor (an
|
|
/// instrument the user put at a position and that stays there). A level mark's `value` is a y
|
|
/// and has nothing to do with either clock.
|
|
std::vector<RowSet::MeasureMark> measure_marks;
|
|
/// `measure_marks` with the time marks converted to display x, rebuilt in place each frame
|
|
/// rather than allocated: this is on the frame tick's path.
|
|
std::vector<RowSet::MeasureMark> measure_marks_display;
|
|
/// What the dump prints for the header and the panels. Captured while the window is alive:
|
|
/// the dump runs from the destroy handler, where every widget call is a GTK-CRITICAL and
|
|
/// reads nothing.
|
|
int dbg_win_btn_w = -1;
|
|
int dbg_bar_w = -1;
|
|
/// The measurements pane's allocation, for the same reason and more urgently: the dump used
|
|
/// to ask the widget itself, which is where the two documented `Gtk-CRITICAL`s came from --
|
|
/// and a critical is the lucky outcome. The pointer is freed memory by then, so a run that
|
|
/// built and destroyed other windows first (`UDPSCOPE_PROBE_HELP_MS`, three of them) has had
|
|
/// that memory reused and the read is a **SIGSEGV** in the middle of the dump.
|
|
int dbg_measures_pane_w = -1;
|
|
int dbg_measures_pane_h = -1;
|
|
bool measures_revealed = false;
|
|
/// The measurement dialog while one is open (null otherwise). A probe needs a handle on it:
|
|
/// it is a window of its own, so the main window's widget tree does not reach its buttons.
|
|
GtkWidget* measure_dialog = nullptr;
|
|
int dbg_measure_step = 0;
|
|
int cursors_pane_mode = -1;
|
|
int cursors_pane_rows = -1;
|
|
unsigned cursors_pane_generation = ~0u;
|
|
/// Which slots were *bound* when the pane was last built, one bit per row/slot
|
|
/// (`kMaxRows * kSlotsPerRow` is 32). `mix` draws a line per bound trace, and a slot's
|
|
/// channel is re-resolved by `bind()` every frame -- so the pane has to follow the bind
|
|
/// and not just the model's generation, or a trace that appears without a structural
|
|
/// change gets no line until something else rebuilds the table.
|
|
std::uint32_t cursors_pane_bound = ~0u;
|
|
|
|
/// The persisted-layout file this data source maps to (--server/--port), a
|
|
/// debounced save handle, and whether the file has been applied once the
|
|
/// CONFIG arrived.
|
|
std::string source_host;
|
|
std::uint16_t source_port = 0;
|
|
/// The configuration of that source this run is on, and its note. A source may have several
|
|
/// named sessions; this is the one its saves go to and the one the dialog marks as current.
|
|
std::string config_name;
|
|
std::string config_comment;
|
|
/// The cache of captured events, and whether latches are being written to it. The store is created
|
|
/// once the source is known (it is keyed on `<host>-<port>`, like the packet capture);
|
|
/// `event_recording` is the trigger panel's switch and defaults on -- the cache is only useful if
|
|
/// it was already on when the event you wanted happened.
|
|
std::unique_ptr<EventStore> event_store;
|
|
/// Whether the cache holds anything, refreshed once a second: what keeps the history button on the bar
|
|
/// for a source whose events are already written, even with recording switched off.
|
|
bool events_cached = false;
|
|
bool event_recording = false;
|
|
/// The events pane: its list, the revealer that holds it, the recording switch (in the trigger
|
|
/// panel, where the trigger's options live), and `events_thumb` -- the picture of the newest
|
|
/// event. Every row carries the picture of *its* event; this is the first one's, which is the one
|
|
/// the probe measures and which the pane does not put anywhere special.
|
|
GtkWidget* events_revealer = nullptr;
|
|
GtkWidget* events_list = nullptr;
|
|
GtkWidget* events_thumb = nullptr;
|
|
GtkWidget* events_record_switch = nullptr;
|
|
/// The toolbar's event-history toggle: the pane is opened *on demand* from here (the item's own
|
|
/// arrangement), and the toggle only exists while trigger mode does -- there are no events without
|
|
/// a trigger, and a button that can never have anything to show is a button spent on nothing.
|
|
GtkWidget* events_toggle = nullptr;
|
|
/// The paned that holds the events pane and the plot, and the width the divider was last dragged
|
|
/// to. 240 by default: enough for the preview and the numbers beside it, and not a plot's worth.
|
|
GtkWidget* events_divider = nullptr;
|
|
int events_pane_width = 240;
|
|
/// The comparison's second plot: its own placeholder, area and `RowSet`, and the paned that holds
|
|
/// the two side by side. `rows_b` exists only once the second area is built, and the area is hidden
|
|
/// (so the paned gives it no room and no handle) unless a comparison is up.
|
|
plot::Plot placeholder_plot_b;
|
|
plot::gtk4::PlotArea* area_b = nullptr;
|
|
GtkWidget* area_b_widget = nullptr;
|
|
/// The figure the second area was last pointed at. `set_figure()` rebuilds its per-row input
|
|
/// controllers, so it is only called when the `RowSet` really has a new figure.
|
|
plot::Figure* area_b_figure = nullptr;
|
|
std::unique_ptr<RowSet> rows_b;
|
|
GtkWidget* plot_split = nullptr;
|
|
/// The builders of the panels inside the window. A widget belongs to the builder that built it, and a
|
|
/// panel lives as long as the window, so these do too -- unlike the dialogs', which are replaced.
|
|
std::vector<GtkBuilder*> ui_builders;
|
|
|
|
/// The main window's builder. Held because the window belongs to its object list: it is replaced on
|
|
/// the next build rather than reused, and everything found by id comes out of it.
|
|
/// The split view the sidebar lives in. Its width, its collapse and its overlay are that widget's
|
|
/// business -- `AdwOverlaySplitView` -- and not a paned position's.
|
|
GtkWidget* side_view = nullptr;
|
|
/// Whether the user wants the sidebar: their last word on the toolbar's button. Kept apart from the
|
|
/// button's own state because that state now *follows* the panel, and the panel is forced off while
|
|
/// there is nothing to show -- without this, connecting a source would leave the sidebar closed.
|
|
bool sidebar_wanted = true;
|
|
/// Set while we are writing the split view's or the button's state ourselves. A *binding* cannot
|
|
/// express this: the widget moves `show-sidebar` on its own when it collapses, the binding pushes that
|
|
/// into the button, and the button's handler then records it as the user's wish -- so the sidebar never
|
|
/// comes back when a source connects. An explicit flag is reliable precisely because our writes go
|
|
/// through `update_side_visibility`, which sets the flag around them.
|
|
bool sidebar_syncing = false;
|
|
|
|
GtkBuilder* window_ui = nullptr;
|
|
bool comparing = false;
|
|
/// The last x/y range seen on each side of a comparison, one entry per row: a frame compares them
|
|
/// with the plots' own to tell *which* side the user moved, and copies from that side to the other.
|
|
/// Without it the mirror would be unconditional and the first copy would undo the gesture.
|
|
std::vector<std::array<double, 4>> sync_last_a;
|
|
std::vector<std::array<double, 4>> sync_last_b;
|
|
/// The two group actions under the pane's header, and what they last put on screen.
|
|
GtkWidget* events_compare = nullptr;
|
|
GtkWidget* events_accumulate = nullptr;
|
|
EventGroupKind group_kind = EventGroupKind::None;
|
|
std::vector<EventRecord> group;
|
|
/// True while a stored event is on screen instead of the live stream.
|
|
bool replaying = false;
|
|
/// Set when a latch frames a new snapshot, consumed at the *end* of that tick: the record needs the
|
|
/// rows bound to the event (for the thumbnail) and the bind happens after the display decision.
|
|
bool record_pending = false;
|
|
/// The waveform file this run opened (`--open`, or the chooser's Waveforms page), or empty for a
|
|
/// live source. Nothing arrives in this mode, so everything that would report on a stream --
|
|
/// the popover's Stream row, the starvation notice -- reports on the file instead.
|
|
std::string waveform_name;
|
|
/// The preferences, as read at start-up and written by the dialog. Held here rather than read where
|
|
/// they are used: the dialog edits a copy, saves it and hands it back, so the file and the running
|
|
/// program cannot disagree about what a preference is.
|
|
Settings settings;
|
|
/// The preferences window while it is up, and its controls: one at a time, like the configurations
|
|
/// dialog -- a second window editing the same settings would let the two disagree.
|
|
/// The preferences window. A `PrefsForm` rather than fields here because the source chooser opens
|
|
/// the same dialog and runs before `App` exists -- see `ui_prefs.hpp`.
|
|
PrefsForm prefs;
|
|
/// The configurations dialog, while it is up: one at a time, and this is what makes a second
|
|
/// `Configurations…` a present rather than a second window.
|
|
GtkWidget* config_dialog = nullptr;
|
|
/// How that source was reached. Unused at runtime -- `Acquisition` has its own copy -- but
|
|
/// written into the session file, because `<host>_<port>.toml` can say neither, and the startup
|
|
/// chooser needs both to reconnect to a multicast source from a stored row.
|
|
std::string source_multicast;
|
|
std::string source_interface;
|
|
guint layout_save_source = 0;
|
|
bool layout_loaded = false;
|
|
unsigned signal_generation = ~0u;
|
|
bool updating_panels = false; ///< guards signal handlers during a rebuild
|
|
bool relayout_pending = false;
|
|
/// True once the renderer's `Style` has been stated (see `tick()`): the renderer does not
|
|
/// exist until the area is realized, so the one-shot has to be a tick's job.
|
|
bool style_stated = false;
|
|
bool signals_panel_initialised = false;
|
|
/// Set once the user edits the trigger level, so the panel stops defaulting it
|
|
/// into the middle of the declared range.
|
|
bool trigger_level_touched = false;
|
|
/// A short-lived message shown in the status line (a drop's outcome), so an
|
|
/// action that changes nothing still says why.
|
|
std::string notice;
|
|
gint64 notice_until_us = 0;
|
|
/// Trigger-mode state for the status line: how many latches were on screen last
|
|
/// frame, and whether an event is held (which is what "frozen" means).
|
|
std::uint64_t seen_latches = 0;
|
|
/// Which snapshot was framed last. Separate from `seen_latches` because a Disarm during
|
|
/// a capture publishes a snapshot without an event having fired.
|
|
std::uint64_t seen_event_seq = 0;
|
|
bool event_held = false;
|
|
/// Whether a capture is open on screen: the edge has been accepted and the post window
|
|
/// is filling in view, on the coming event's own clock. It is what keeps
|
|
/// `clamp_to_data()` off the pinned axis and tells the status line apart from "armed".
|
|
bool capturing = false;
|
|
/// The edge of the capture `capturing` refers to, so a second edge accepted before the
|
|
/// first capture closed re-frames instead of being mistaken for the same one.
|
|
double capture_edge = 0.0;
|
|
/// Monotonic time of the last event, and the edge that started the clock. The clock
|
|
/// starts when the *edge* is accepted, not when the capture closes -- that is the
|
|
/// instant the user saw -- and the edge is kept so the latch that ends a live-updated
|
|
/// capture does not restart 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 event of the session.
|
|
gint64 last_latch_us = 0;
|
|
double timed_edge = 0.0;
|
|
double window_limit_s = -1.0; ///< the cap the window control was last clamped against
|
|
/// The rate each channel actually *stored* over the last second, and the declared rate
|
|
/// beside it. The two differ for two reasons now and both are worth seeing: the declared
|
|
/// rate is a claim the stream may not keep, and a window too long to hold at the wire
|
|
/// rate is resampled on ingest (`Partition::time_stride`), so this is also the readout
|
|
/// of how much resolution the current window costs. 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.
|
|
int dbg_axes_x_min = 1 << 20;
|
|
int dbg_axes_x_max = -(1 << 20);
|
|
int dbg_axes_frames = 0;
|
|
/// Frames on which some row's x window did not match row 0's. The rows share the
|
|
/// link group's one window, so this is an assertion: it must read 0.
|
|
int dbg_x_desync = 0;
|
|
gint64 last_channels_meas = 0;
|
|
/// Starvation detector: the delivered-frame count and the client's reconnect count a
|
|
/// second ago. A source that serves one client at a time shows up as a reconnect plus
|
|
/// no frames for the second between two samples (see `note_starvation`).
|
|
std::uint64_t starve_frames = 0;
|
|
bool starve_have_prev = false;
|
|
/// Consecutive one-second samples with `frames` unchanged while connected and running.
|
|
int starve_still = 0;
|
|
/// Set while the starvation notice is up, so the stderr line prints on the edges.
|
|
bool starve_reported = false;
|
|
/// Frames drawn with every bound trace slot empty although its channel has
|
|
/// samples: the view was swapped and not yet published.
|
|
int dbg_blank_frames = 0;
|
|
/// The *renderer's* blank: a frame that staged no trace geometry while every bound trace held
|
|
/// samples. `dbg_blank_frames` cannot see this one -- it asks the rings, and a ring that holds
|
|
/// data while the renderer draws nothing is exactly the "the data disappears for a frame" glitch.
|
|
int dbg_render_blank_frames = 0;
|
|
/// The last frame's staged segment count, so the tick can judge the frame the renderer drew.
|
|
unsigned long long dbg_seg_last = 0;
|
|
/// Frames the one area staged: a figure is one `::render` for every row.
|
|
int dbg_frames_painted = 0;
|
|
/// Where a frame's time goes, split at the two boundaries that can be observed from
|
|
/// here: `tick` is this function (bind, acquire, clamp, status), `draw` is everything
|
|
/// the plot renderer did (measured from the frame clock's `before-paint` to the return
|
|
/// of `PlotArea`'s own `::render` handler, which is why the adapter returns FALSE from
|
|
/// it), and `paint` is the whole GTK paint including the toolkit compositing the GL
|
|
/// area's framebuffer. A complaint about lag names one of the three; a summary that
|
|
/// does not separate them cannot say which.
|
|
int dbg_tick_n = 0;
|
|
double dbg_tick_ms_sum = 0.0;
|
|
double dbg_tick_ms_max = 0.0;
|
|
int dbg_paint_n = 0;
|
|
double dbg_paint_ms_sum = 0.0;
|
|
double dbg_paint_ms_max = 0.0;
|
|
double dbg_draw_ms_sum = 0.0;
|
|
double dbg_draw_ms_max = 0.0;
|
|
/// Which paint the worst `draw` was. A mean far below the max says the cost is
|
|
/// concentrated in a few frames but not *which*, and the two answers need opposite
|
|
/// responses: an early frame is the warm-up (the glyph atlas bake and the first
|
|
/// vertex-buffer growth land on it), a late one is a frame whose own geometry was
|
|
/// expensive and is the thing to chase.
|
|
int dbg_draw_max_frame = -1;
|
|
gint64 dbg_paint_start_us = 0;
|
|
GdkFrameClock* dbg_clock = nullptr;
|
|
/// The renderer's own account of what each frame cost it: whether the traces went
|
|
/// through the LOD pyramid or were drawn raw, and how much geometry that staged. The
|
|
/// *path* is the first question to ask of a slow frame -- `raw` frames are O(visible
|
|
/// samples) by construction.
|
|
std::uint64_t dbg_seg_sum = 0;
|
|
std::uint32_t dbg_seg_max = 0;
|
|
std::uint64_t dbg_visible_max = 0;
|
|
int dbg_raw_frames = 0;
|
|
int dbg_lod_frames = 0;
|
|
/// Frames on which at least one trace's geometry was reduced onto the pixel grid (one
|
|
/// vertical segment per column). It is orthogonal to `raw`/`lod` on purpose -- the
|
|
/// reduction is gated on the *density* of the gathered points, not on which level
|
|
/// produced them -- and it is the only reading that distinguishes "the segment count
|
|
/// collapsed because the reduction ran" from "the window got narrower".
|
|
int dbg_column_frames = 0;
|
|
/// Frames on which at least one reduced column was drawn **graded by dwell** rather than
|
|
/// as one flat band (`Style::intensity_grading`, which this app turns on). A subset of
|
|
/// `columns`: grading is a way of drawing the reduction, not a third path, so the two
|
|
/// counts being equal is what says the look is reaching every reduced frame.
|
|
int dbg_graded_frames = 0;
|
|
/// Painted frames on which `draw_traces` drew **no trace at all**, and the last one's index.
|
|
/// This is the observable for "the plot flickers as if there were no data": `staged_segments`
|
|
/// cannot see it, because the grid and the instruments stage geometry of their own, so a frame
|
|
/// that drew every chrome line and not one sample still reports tens of thousands of segments.
|
|
/// A count that is only the frames before the first bind is startup; one that keeps growing
|
|
/// afterwards is the glitch.
|
|
int dbg_notrace_frames = 0;
|
|
int dbg_notrace_last = -1;
|
|
/// The widest *minimum* width the side column or either pane has ever demanded. The side
|
|
/// column is a fixed 320 px by design, so a figure far above that is a label whose text drove
|
|
/// its own minimum -- the "layout must not depend on content" rule broken, and the only known
|
|
/// mechanism by which the plot's allocation can exceed the window.
|
|
int dbg_min_w_worst = 0;
|
|
int dbg_min_w_reports = 0;
|
|
/// Buffer rebuilds the tick has seen. Each one drops the history behind every trace.
|
|
unsigned dbg_generation_prev = 0;
|
|
bool dbg_generation_seen = false;
|
|
int dbg_generation_changes = 0;
|
|
/// Set by the tick once a bind has put samples on a trace, so the counter above can tell the
|
|
/// frames before any data from the ones after.
|
|
bool dbg_ever_bound = false;
|
|
int dbg_notrace_after_bound = 0;
|
|
/// The frame's one `DisplayMode`, recorded by the tick so the paint probe can name the
|
|
/// state a frame was drawn in without taking a second read of it (the producer can latch
|
|
/// between any two, which is why the mode is decided once per frame).
|
|
DisplayMode dbg_display = DisplayMode::Live;
|
|
/// The **ink** account, recomputed per painted frame the renderer's own way (see
|
|
/// `check_frame_ink`). `staged_segments` and `lod_traces` cannot see a waveform that
|
|
/// vanished for a frame -- the grid and the instruments stage geometry of their own, and a
|
|
/// trace whose gathered points all collapse onto one x is still "drawn" -- so these count
|
|
/// the polyline the filter actually leaves and how much of the axis it spans.
|
|
int dbg_ink_checked = 0;
|
|
int dbg_ink_collapse = 0; ///< fewer than two distinct positions left: nothing to draw
|
|
int dbg_ink_staircase = 0; ///< the filter dropped over three quarters of the points
|
|
int dbg_ink_short = 0; ///< the ink reached under a quarter of the axis' width
|
|
double dbg_ink_worst_cover = 1.0;
|
|
/// The worst distance, in pixels, between where a sample is *drawn* and the x a readout
|
|
/// quotes for the same sample. `gather()` stages x as an f32 relative to an origin the
|
|
/// renderer names, while `Plot::crosshair_point()` reads `x_at()` in exact f64, so this is
|
|
/// the quantisation of that f32 expressed in the unit the user sees it in. Measured on the
|
|
/// raw levels only, where a gathered point *is* a sample; above them a point is a bucket's
|
|
/// envelope and has no single sample to be off from.
|
|
double dbg_ink_worst_offset_px = 0.0;
|
|
int dbg_ink_raw_checked = 0; ///< frames the figure above was measured over
|
|
int dbg_ink_reports = 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[4] = {0, 0, 0, 0};
|
|
int dbg_reset_visible = -1;
|
|
std::vector<std::uint64_t> last_stored;
|
|
std::uint64_t dbg_latches_at_disarm = 0;
|
|
std::uint64_t dbg_seq_at_disarm = 0;
|
|
bool dbg_capturing_at_disarm = false;
|
|
int dbg_disarm_polls = 0;
|
|
int dbg_disarm_in_capture = 0;
|
|
int dbg_swaps = 0;
|
|
int dbg_axes_w_min = 1 << 20;
|
|
int dbg_axes_w_max = -(1 << 20);
|
|
int dbg_axes_w_prev = -1;
|
|
int dbg_axes_w_changes = 0;
|
|
/// What the pan probe asked for, so the frame after it can be compared against it: a capture
|
|
/// during the probe is not a value either side can recompute, and a capturing lambda cannot be
|
|
/// a `GSourceFunc`.
|
|
double dbg_pan_asked_lo = 0.0;
|
|
double dbg_pan_asked_hi = 0.0;
|
|
bool dbg_pan_asked = false;
|
|
/// Which gesture the probe is on: 1 = the pan was asked and its verdict is due, 2 = the
|
|
/// zoom-out was asked and its verdict is due.
|
|
int dbg_gesture_stage = 0;
|
|
/// A verdict the *tick* owes, one frame after a gesture was asked: 1 pan, 2 zoom-out, 3
|
|
/// zoom-in. Reported from the frame loop rather than a `g_timeout`, because a 300 ms timer
|
|
/// slips past the state it was meant to observe as soon as the frames are heavy -- which is
|
|
/// exactly the load this probe runs under.
|
|
int dbg_gesture_verdict = 0;
|
|
/// The event-zoom probe's remaining steps, counted down by the *tick* for the same reason
|
|
/// the gesture verdicts are: each step's reading has to be taken after the frame's pin and
|
|
/// clamp have run, or it reports the ask rather than the view.
|
|
int dbg_zoom_steps = 0;
|
|
int dbg_zoom_step_no = 0;
|
|
int dbg_axes_w_change_frame = -1;
|
|
int dbg_axes_w_change_side = -1;
|
|
float dbg_scale_min = 1e9f;
|
|
float dbg_scale_max = -1e9f;
|
|
int dbg_vp_w_min = 1 << 20;
|
|
int dbg_vp_w_max = -(1 << 20);
|
|
int dbg_xtick_min = 1 << 20;
|
|
int dbg_xtick_max = -(1 << 20);
|
|
int dbg_gutter_min = 1 << 20;
|
|
int dbg_gutter_max = -(1 << 20);
|
|
int dbg_side_w = 0;
|
|
int dbg_side_h = 0;
|
|
int dbg_signal_w = 0;
|
|
int dbg_signal_h = 0;
|
|
int dbg_signal_row_h = 0;
|
|
int dbg_trigger_w = 0;
|
|
int dbg_trigger_h = 0;
|
|
int dbg_signal_revealed = 0;
|
|
int dbg_trigger_revealed = 0;
|
|
int dbg_side_visible = 0;
|
|
int dbg_trigger_items = 0;
|
|
int dbg_trigger_selected = -1;
|
|
int dbg_side_scroll_after_hide = 1;
|
|
std::string dbg_mode_triggered;
|
|
std::string dbg_window_text;
|
|
double dbg_marker_x = 0.0;
|
|
double dbg_marker_max = 0.0;
|
|
double dbg_trigger_time = 0.0;
|
|
int dbg_trigger_time_valid = -1;
|
|
std::string dbg_mode_live;
|
|
int dbg_toggles_visible = 0;
|
|
int dbg_trigger_revealed_default = 0;
|
|
|
|
/// The detail string the popover and the exit dump read. The dump used to read the label,
|
|
/// which no longer carries these fields.
|
|
std::string status_detail;
|
|
std::size_t status_detail_max = 0;
|
|
/// The widest glance line this run produced. The bar sits above the plot, so this is the
|
|
/// "layout must not depend on content" reading for the status row.
|
|
std::size_t glance_max_chars = 0;
|
|
/// The last glance line, for the exit dump's `status bar:` line — the dump runs after the
|
|
/// window is destroyed, so it cannot ask the label.
|
|
std::string dbg_glance_last;
|
|
/// Which step of the menu probe runs next (`UDPSCOPE_PROBE_MENUS_MS`).
|
|
int dbg_menus_step = 0;
|
|
/// Which step of the capture probe runs next (`UDPSCOPE_PROBE_CAPTURE_MS`).
|
|
int dbg_capture_step = 0;
|
|
/// Which step of the event-cache probe runs next (`UDPSCOPE_PROBE_EVENTS_MS`), and the bounded wait
|
|
/// it keeps for a second latch (it is counting retries, not time).
|
|
int dbg_events_step = 0;
|
|
int dbg_events_wait = 0;
|
|
/// Which step of the plot-context-menu probe runs next (`UDPSCOPE_PROBE_ROWMENU_MS`), and the
|
|
/// popover it is measuring.
|
|
int dbg_rowmenu_step = 0;
|
|
GtkWidget* row_menu_popover = nullptr;
|
|
|
|
GtkWidget* status_info_button = nullptr; ///< the ⓘ
|
|
/// The packet-capture toggle, in the detail popover. `toggled` sets the producer's
|
|
/// atomic and nothing else; the tick keeps it honest about connection and failures.
|
|
GtkWidget* capture_button = nullptr;
|
|
/// What the toggle was last set to from this thread, so a producer-side failure
|
|
/// (a full disk) can return the button to off without re-entering its handler.
|
|
bool capture_want_ui = false;
|
|
GtkWidget* status_popover = nullptr;
|
|
/// The popover's thirteen labels: five row headings and eight value lines. Built once; the
|
|
/// tick sets their text. A rebuild here would be a per-frame relayout of a visible widget.
|
|
GtkWidget* popover_label[13] = {};
|
|
int popover_rebuilds = 0;
|
|
};
|
|
struct ConfigDialog {
|
|
App* app = nullptr;
|
|
GtkWidget* window = nullptr;
|
|
GtkWidget* list = nullptr;
|
|
GtkWidget* name = nullptr;
|
|
GtkWidget* comment = nullptr;
|
|
};
|
|
struct WindowUnit {
|
|
const char* name;
|
|
double seconds;
|
|
};
|
|
enum class BadgePhase { Lit = 0, Dim = 1, Idle = 2, Connecting = 3 };
|
|
|
|
struct CalibContext {
|
|
App* app;
|
|
std::size_t signal;
|
|
GtkWidget* gain;
|
|
GtkWidget* offset;
|
|
GtkWidget* window;
|
|
};
|
|
struct DragContext {
|
|
App* app;
|
|
std::size_t signal;
|
|
};
|
|
struct ColorPick {
|
|
App* app;
|
|
std::string signal;
|
|
};
|
|
class MeasureWindowReader {
|
|
public:
|
|
MeasureWindowReader(App& app, std::size_t channel) {
|
|
const RowSet::Row& r0 = app.rows->row(0);
|
|
if (r0.plot == nullptr) return;
|
|
const plot::Range view = r0.plot->x_range(r0.axis_x);
|
|
if ((snap_ = app.model->display_snapshot()) != nullptr) {
|
|
if (channel >= snap_->values.size()) return;
|
|
const std::vector<f64>& st = snap_->stamps;
|
|
if (snap_->values[channel].size() != st.size()) return;
|
|
const auto lo = std::lower_bound(st.begin(), st.end(), view.min);
|
|
const auto hi = std::upper_bound(st.begin(), st.end(), view.max);
|
|
if (hi <= lo) return;
|
|
off_ = static_cast<std::size_t>(lo - st.begin());
|
|
count_ = static_cast<std::size_t>(hi - lo);
|
|
snap_vals_ = &snap_->values[channel];
|
|
// The snapshot's pyramid is built from sample 0 and nothing is ever dropped from it, so
|
|
// its absolute index space *is* the snapshot's.
|
|
if (channel < snap_->pyramids.size()) pyr_ = &snap_->pyramids[channel];
|
|
origin_ = off_;
|
|
return;
|
|
}
|
|
lock_ = std::unique_lock<std::mutex>(app.model->mutex());
|
|
if (channel >= app.model->channel_count()) return;
|
|
epoch_ = app.model->display_epoch();
|
|
u64 first = 0;
|
|
u64 last = 0;
|
|
app.model->live_window_locked(view.min - epoch_, view.max - epoch_, first, last);
|
|
if (last <= first) return;
|
|
store_ = &app.model->live();
|
|
channel_ = channel;
|
|
first_ = first;
|
|
count_ = static_cast<std::size_t>(last - first);
|
|
// The channel's own pyramid, in the value ring's absolute index space -- the same space
|
|
// `live_window_locked()` answers in, since a channel's stride is 1. It is current as of this
|
|
// frame's `acquire_all()`; the samples produced since are past `window_end()` and are read
|
|
// raw, which bounds that tail at one frame of production.
|
|
pyr_ = &store_->channel(channel).pyramid();
|
|
origin_ = first;
|
|
}
|
|
|
|
std::size_t size() const noexcept { return count_; }
|
|
|
|
/// The whole level-0 LOD buckets lying inside this window, as absolute bucket indices
|
|
/// `[first, last)`. False when the window has none -- a window shorter than a bucket, or one
|
|
/// the pyramid does not reach.
|
|
bool bucket_range(u64& first, u64& last) const {
|
|
if (pyr_ == nullptr || count_ == 0) return false;
|
|
const u64 begin = std::max<u64>(origin_, pyr_->window_base());
|
|
const u64 end = std::min<u64>(origin_ + count_, pyr_->window_end());
|
|
if (end <= begin) return false;
|
|
const u64 stored = pyr_->level_base(0);
|
|
first = std::max<u64>(stored, (begin + plot::data::kBucket - 1) / plot::data::kBucket);
|
|
last = std::min<u64>(stored + pyr_->level_size(0), end / plot::data::kBucket);
|
|
return last > first;
|
|
}
|
|
|
|
/// Window offset of absolute bucket `k`'s first sample.
|
|
std::size_t bucket_offset(u64 k) const noexcept {
|
|
return static_cast<std::size_t>(k * plot::data::kBucket - origin_);
|
|
}
|
|
|
|
MeasureBucket bucket(u64 k) const {
|
|
const plot::data::MinMax mm = pyr_->bucket(0, k);
|
|
MeasureBucket out;
|
|
out.lo = static_cast<double>(mm.min);
|
|
out.hi = static_cast<double>(mm.max);
|
|
out.sum = pyr_->bucket_sum(0, k);
|
|
out.count = static_cast<std::size_t>(pyr_->bucket_count(0, k));
|
|
out.offset = bucket_offset(k);
|
|
out.span = plot::data::kBucket;
|
|
return out;
|
|
}
|
|
|
|
/// `n` values from window offset `at`. The pointer is valid until the next call.
|
|
const f32* values(std::size_t at, std::size_t n) {
|
|
if (snap_vals_ != nullptr) return snap_vals_->data() + off_ + at;
|
|
if (scratch_.size() < n) scratch_.resize(n);
|
|
store_->values(channel_).copy_from(first_ + at, n, scratch_.data());
|
|
return scratch_.data();
|
|
}
|
|
|
|
/// Window sample `i`'s stream time. One sample at a time on purpose: see the class comment.
|
|
double time_of(std::size_t i) const {
|
|
if (snap_vals_ != nullptr) return snap_->stamps[off_ + i];
|
|
f64 stamp = 0.0;
|
|
store_->time().copy_from(first_ + i, 1, &stamp);
|
|
return epoch_ + stamp;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<const EventSnapshot> snap_;
|
|
const std::vector<f32>* snap_vals_ = nullptr;
|
|
std::size_t off_ = 0;
|
|
std::unique_lock<std::mutex> lock_;
|
|
BufferStore* store_ = nullptr;
|
|
std::size_t channel_ = 0;
|
|
u64 first_ = 0;
|
|
std::size_t count_ = 0;
|
|
f64 epoch_ = 0.0;
|
|
const plot::data::MinMaxPyramid* pyr_ = nullptr;
|
|
/// Absolute sample index of window offset 0, so a bucket's absolute span becomes an offset.
|
|
u64 origin_ = 0;
|
|
std::vector<f32> scratch_;
|
|
};
|
|
struct MeasureDialog {
|
|
App* app = nullptr;
|
|
int edit = -1;
|
|
GtkWidget* signal_drop = nullptr;
|
|
GtkWidget* type_drop = nullptr;
|
|
GtkWidget* threshold = nullptr;
|
|
GtkWidget* above = nullptr;
|
|
GtkWidget* show = nullptr;
|
|
GtkWidget* window = nullptr;
|
|
/// The option rows' boxes and labels, so the dialog can *retitle* them per type: the same
|
|
/// switch means "the pulse is above the level" for a pulse and nothing at all for a period,
|
|
/// and the difference has to be in words, not just in visibility.
|
|
GtkWidget* threshold_row = nullptr;
|
|
GtkWidget* threshold_label = nullptr;
|
|
GtkWidget* side_row = nullptr;
|
|
GtkWidget* side_label = nullptr;
|
|
/// The names the signal dropdown was built from, in order. `apply` resolves an index
|
|
/// through *this*, not through the model's signal list: the dropdown lists the plotted
|
|
/// traces, so index `i` here is the i-th trace -- indexing the CONFIG with it selected
|
|
/// whatever signal happened to sit at that position there (the stream's timestamp, for the
|
|
/// first entry, which is what picking the first trace used to store).
|
|
std::vector<std::string> signals;
|
|
};
|
|
struct MathWireRef {
|
|
int node = -1;
|
|
int which = 0;
|
|
};
|
|
enum class MathDrag { None, Node, Wire, Pan };
|
|
|
|
struct MathDialog {
|
|
App* app = nullptr;
|
|
int edit = -1;
|
|
std::vector<MathNode> nodes;
|
|
int output = 0;
|
|
int selected = 0;
|
|
/// What a drag is doing, decided once at `drag-begin` from what is under the press and not
|
|
/// revisited: a gesture that changes its mind mid-drag is unusable. `Wire` carries a loose end
|
|
/// in graph units, which the draw shows so the user can see what is being connected.
|
|
MathDrag drag_mode = MathDrag::None;
|
|
int wire_from = -1;
|
|
int wire_detach = -1;
|
|
int wire_detach_which = 0;
|
|
double wire_x = 0.0;
|
|
double wire_y = 0.0;
|
|
double pan_x0 = 0.0;
|
|
double pan_y0 = 0.0;
|
|
/// `Wire`: the press point in canvas coordinates. The gesture's `dx`/`dy` is relative to this
|
|
/// point, so `press_cx + dx` is always the pointer, even on a detach where `wire_from` is a
|
|
/// different node from the one the pointer touched.
|
|
double press_cx = 0.0;
|
|
double press_cy = 0.0;
|
|
/// Where the box being dragged *was* when the drag began, and which box it is. The gesture
|
|
/// reports the total offset from its own start point, so adding it to a value that the previous
|
|
/// update already moved is a runaway: the box accelerates away from the pointer and leaves the
|
|
/// canvas. `-1` means the drag did not begin on a box.
|
|
double grab_x = 0.0;
|
|
double grab_y = 0.0;
|
|
int drag_node = -1;
|
|
/// The wire `Delete` would remove, or `{-1, 0}`.
|
|
MathWireRef selected_wire{-1, 0};
|
|
/// The view: canvas pixels per graph unit, and where graph (0,0) is drawn. Applied once to the
|
|
/// cairo context by `math_canvas_draw()`; every hit test runs the inverse (`math_to_graph()`)
|
|
/// rather than comparing a pointer position against a graph one, and sizes its tolerance in
|
|
/// *screen* pixels (`kPort / view_scale`) -- a tolerance left in graph units shrinks with the
|
|
/// view and makes a port unhittable zoomed out.
|
|
double view_scale = 1.0;
|
|
double view_x = 0.0;
|
|
double view_y = 0.0;
|
|
GtkWidget* window = nullptr;
|
|
GtkWidget* canvas = nullptr;
|
|
GtkWidget* name = nullptr;
|
|
GtkWidget* unit = nullptr;
|
|
GtkWidget* op_drop = nullptr;
|
|
GtkWidget* source_drop = nullptr;
|
|
GtkWidget* gain = nullptr;
|
|
GtkWidget* offset = nullptr;
|
|
GtkWidget* fc = nullptr;
|
|
GtkWidget* q = nullptr;
|
|
GtkWidget* lo = nullptr;
|
|
GtkWidget* hi = nullptr;
|
|
GtkWidget* rows[6] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
|
|
GtkWidget* inputs = nullptr;
|
|
/// The validation line and the button it governs. Both are set from `math_dialog_sync()` and from
|
|
/// nowhere else, so what the line says and what the button allows cannot disagree.
|
|
GtkWidget* problem = nullptr;
|
|
GtkWidget* apply = nullptr;
|
|
/// Why the last gesture was *refused* -- "a node cannot read itself", or a cycle. A refusal has no
|
|
/// standing state to be read back from: it is a thing that did not happen, so `math_dialog_problem()`
|
|
/// cannot describe it and a refused wire otherwise just fails to appear, which reads as the editor
|
|
/// being broken. `math_dialog_sync()` shows this in preference to the graph's own problem and
|
|
/// **consumes it as it writes**, so the next sync -- and every mutation syncs -- replaces it with
|
|
/// the graph's state. `Apply`'s sensitivity still follows `math_dialog_problem()` alone: a refused
|
|
/// wire changed nothing, so a graph that was saveable still is.
|
|
std::string refusal;
|
|
/// True while the form is being written. `gtk_drop_down_set_selected()` emits `notify::selected`
|
|
/// for a programmatic set exactly as for a user one, and the op handler answers a notify by
|
|
/// syncing -- so writing the form **re-entered** `math_dialog_sync()`, and the inner call rewrote
|
|
/// the validation label the outer one had just written. That is how a refusal vanished in the same
|
|
/// call that put it up (the probe's `wire: self through drag-end` read an empty line). A
|
|
/// programmatic set is not a user edit.
|
|
bool syncing = false;
|
|
};
|
|
struct ExportContext {
|
|
App* app = nullptr;
|
|
ExportTable table;
|
|
};
|
|
struct FixedRangeContext {
|
|
App* app;
|
|
int row;
|
|
GtkWidget* lo;
|
|
GtkWidget* hi;
|
|
GtkWidget* window;
|
|
};
|
|
struct RemoveTarget {
|
|
static constexpr int kAllSlots = -1;
|
|
App* app;
|
|
int row;
|
|
int slot;
|
|
};
|
|
enum class RowMenuView { Main, Remove };
|
|
|
|
struct RowMenuState {
|
|
App* app = nullptr;
|
|
int row = 0;
|
|
GtkWidget* popover = nullptr;
|
|
GtkWidget* box = nullptr;
|
|
RowMenuView view = RowMenuView::Main;
|
|
};
|
|
struct EventSamples {
|
|
std::vector<std::string> names;
|
|
std::vector<f64> times;
|
|
std::vector<std::vector<f32>> columns; ///< [column][row]
|
|
};
|
|
using EventThumbCache = std::unordered_map<std::string, GdkTexture*>;
|
|
struct ConditionProbe {
|
|
double timeout = 0.300;
|
|
/// 0 = pulse width `<` (the glitch), 1 = pulse width `>`, 2 = window, 3 = dropout. Selected
|
|
/// by `UDPSCOPE_PROBE_CONDITION_SHAPE`; the default keeps Task 3's glitch leg.
|
|
int shape = 0;
|
|
int waited = 0;
|
|
};
|
|
struct RowMenuProbeState {
|
|
int main_rows = 0;
|
|
int main_bad = 0;
|
|
int remove_rows = 0;
|
|
int remove_bad = 0;
|
|
int back_rows = 0;
|
|
int back_bad = 0;
|
|
int second_rows = 0;
|
|
int second_bad = 0;
|
|
int remove_row_in_main = 0;
|
|
int remove_row_gone = 0;
|
|
int can_open_again = 0;
|
|
};
|
|
|
|
constexpr int kTickMs = 16;
|
|
/// Below this window width the sidebar has no room beside the plot, and the split view overlays it
|
|
/// instead of squeezing it. See `tick()`.
|
|
constexpr int kSidebarRoom = 900;
|
|
constexpr int kWindowUnitCount = 3;
|
|
constexpr double kWindowMinS = 1.0e-6;
|
|
constexpr int kWindowLabelChars = 9;
|
|
constexpr int kRowKeyBias = 1;
|
|
constexpr int kLayoutButtonPx = 24;
|
|
constexpr int kLayoutButtonPad = 4;
|
|
constexpr const char* kLayoutKindKey = "udpscope-layout-insert";
|
|
|
|
/// The ↕ handles' own key: what they are is *not* what the `+` is, and `sync_layout_editor()`
|
|
/// places them differently -- one boundary, two controls.
|
|
constexpr const char* kLayoutResizeKey = "udpscope-layout-resize";
|
|
constexpr gint64 kMeasureRefreshUs = 100 * 1000;
|
|
constexpr gint64 kMeasureHeavyUs = 5 * 1000;
|
|
constexpr const char* kCursorModeNames[] = {"vertical", "horizontal", "both", "mix"};
|
|
constexpr double kNodeW = 132.0;
|
|
constexpr double kNodeH = 46.0;
|
|
constexpr double kPort = 10.0;
|
|
constexpr int kPortOut = -1;
|
|
constexpr double kMathScaleMin = 0.25;
|
|
constexpr double kMathScaleMax = 4.0;
|
|
constexpr double kMathZoomStep = 1.1;
|
|
constexpr double kWirePick = 6.0;
|
|
constexpr int kZoomGestureSteps = 8;
|
|
constexpr int kZoomWindowSteps = 8;
|
|
constexpr int kEventThumbW = 138;
|
|
constexpr int kEventThumbH = 46;
|
|
inline int g_stats_phase = 0;
|
|
inline std::vector<std::uint64_t> g_stats_n_before;
|
|
inline double g_trigrenum_edge = 0.0;
|
|
inline std::uint64_t g_trigrenum_latches = 0;
|
|
inline ConditionProbe g_condition;
|
|
inline double g_search_t0 = 0.0;
|
|
constexpr int kMenuRowMaxPx = 30;
|
|
inline RowMenuProbeState g_rowmenu_probe;
|
|
|
|
void install_css();
|
|
void on_menu_row_toggled(GtkToggleButton* button, gpointer);
|
|
GtkWidget* menu_row(const char* label, const char* accel, bool with_check, GtkWidget** out_check);
|
|
GtkWidget* menu_button(const char* label, const char* accel);
|
|
GtkWidget* menu_toggle(const char* label, const char* accel, bool active);
|
|
GtkWidget* menu_separator();
|
|
GtkWidget* labeled_box(const char* label, GtkWidget* child);
|
|
/// Loads a UI file into the window's builders and returns the builder, so the caller can resolve the
|
|
/// objects it needs by id. See `ui_widgets.cpp`.
|
|
GtkBuilder* ui_load(App& app, const char* resource);
|
|
LayoutState collect_layout(App& app);
|
|
void on_config_row_activated(GtkListBox*, GtkListBoxRow* row, gpointer user);
|
|
void on_config_forget_clicked(GtkButton* button, gpointer user);
|
|
void on_config_save_as(GtkButton*, gpointer user);
|
|
void config_dialog_refresh(ConfigDialog& d);
|
|
void open_configurations(App& app);
|
|
void save_layout_now(App& app);
|
|
bool save_configuration_as(App& app, const std::string& name, const std::string& comment);
|
|
gboolean layout_save_tick(gpointer user);
|
|
void schedule_layout_save(App& app);
|
|
void apply_layout(App& app, const LayoutState& st);
|
|
GtkWidget* labeled_box_fixed(const char* label, GtkWidget* child);
|
|
GtkWidget* spin(double min, double max, double step, int digits, double value);
|
|
GtkWidget* dialog_window(GtkWidget* parent, const char* title);
|
|
void dialog_add_button(GtkWidget* window, GtkWidget* button);
|
|
void on_popover_closed(GtkPopover* popover, gpointer);
|
|
int window_unit_for(double s);
|
|
std::string format_window_value(double s, int unit);
|
|
std::string format_window(double s);
|
|
const char* badge_markup(BadgePhase phase);
|
|
std::string format_measure(double v, const std::string& unit);
|
|
void set_notice(App& app, std::string what, int seconds = 4);
|
|
void set_row_key(GtkWidget* widget, int row);
|
|
int row_key(GtkWidget* widget);
|
|
int row_at(const App& app, double x, double y);
|
|
void cancel_drag_on(App& app, int row);
|
|
void report_wide_widgets(GtkWidget* root, int threshold, int depth);
|
|
const char* display_mode_name(DisplayMode m);
|
|
void check_frame_ink(App& app);
|
|
int on_render_probe(GtkGLArea*, GdkGLContext*, gpointer user);
|
|
void on_before_paint(GdkFrameClock*, gpointer user);
|
|
void on_after_paint(GdkFrameClock*, gpointer user);
|
|
GtkWidget* make_compare_area(App& app);
|
|
GtkWidget* make_area(App& app);
|
|
void relayout_areas(App& app);
|
|
void clear_layout_editor(App& app);
|
|
void on_layout_row_remove(GtkButton* button, gpointer user);
|
|
void on_layout_row_insert(GtkButton* button, gpointer user);
|
|
void sync_layout_editor(App& app);
|
|
void rebuild_layout_editor(App& app);
|
|
void on_layout_editor_done(GtkButton*, gpointer user);
|
|
void on_layout_editor_toggled(GtkToggleButton* button, gpointer user);
|
|
void on_signal_switch(GObject* obj, GParamSpec*, gpointer user);
|
|
void on_arm_clicked(GtkButton* button, gpointer user);
|
|
TriggerType trigger_type_at(guint i);
|
|
WidthCmp width_cmp_at(guint i);
|
|
void publish_trigger_fields(App* app);
|
|
void sync_trigger_rows(App& app);
|
|
void on_trigger_field_changed(GtkWidget*, gpointer user);
|
|
void on_trigger_drop_changed(GObject*, GParamSpec*, gpointer user);
|
|
void on_trigger_shape_changed(GObject*, GParamSpec*, gpointer user);
|
|
void on_trigger_signal_changed(GObject* obj, GParamSpec*, gpointer user);
|
|
void show_info_dialog(GtkWindow* parent, const SignalInfo& sig);
|
|
void on_info_clicked(GtkButton* button, gpointer user);
|
|
void on_calibrate_apply(GtkButton*, gpointer user);
|
|
void open_calibration(App& app, GtkWindow* parent, std::size_t signal);
|
|
GdkContentProvider* on_drag_prepare(GtkDragSource*, double, double, gpointer user);
|
|
int signal_color_index(const std::vector<SignalInfo>& signals, std::size_t signal);
|
|
std::string color_hex(plot::Color c);
|
|
std::string chip_markup(plot::Color c);
|
|
bool signal_plotted(App& app, const std::string& name);
|
|
std::string signal_chip_markup(App& app, const std::vector<SignalInfo>& signals, std::size_t signal);
|
|
void on_signal_color_chosen(GObject* source, GAsyncResult* res, gpointer user);
|
|
void open_signal_color(App& app, std::size_t signal);
|
|
void rebuild_signal_list(App& app, const std::vector<SignalInfo>& signals);
|
|
void rebuild_trigger_signals(App& app);
|
|
double window_limit(const App& app);
|
|
void sync_window_display(App& app, double seconds);
|
|
void reset_measure_stats(App& app);
|
|
void apply_time_window(App& app, double seconds);
|
|
void on_window_entry_activate(GtkEntry* entry, gpointer user);
|
|
void on_window_unit_changed(GObject* obj, GParamSpec*, gpointer user);
|
|
void on_window_step(GtkButton* button, gpointer user);
|
|
void refresh_window_limits(App& app);
|
|
void on_pause_toggled(GtkToggleButton* button, gpointer user);
|
|
void search_jump(App& app, double t);
|
|
void update_search_panel(App& app);
|
|
void sync_events_availability(App& app);
|
|
/// Restates the events pane's divider position once a frame; see the definition.
|
|
void sync_events_divider(App& app);
|
|
/// The preferences dialog: one window, opened from the overflow menu. `apply_theme()` is separate
|
|
/// because the theme is the one preference that takes effect without a restart.
|
|
void open_preferences(App& app);
|
|
gboolean prefs_probe(gpointer user);
|
|
gboolean sidebar_probe(gpointer user);
|
|
gboolean boxzoom_probe(gpointer user);
|
|
/// A labelled row whose control keeps its natural width -- what a `GtkSwitch` needs and what
|
|
/// `labeled_box()`'s expanding child denies it.
|
|
GtkWidget* labeled_switch_row(const char* label, GtkWidget* control);
|
|
/// The layout editor's boundary drag: the press starts it, movement sizes the two rows it separates.
|
|
void on_layout_drag_begin(GtkGestureDrag* gesture, double x, double y, gpointer user);
|
|
void on_layout_drag_update(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer user);
|
|
void on_layout_drag_end(GtkGestureDrag* gesture, double offset_x, double offset_y, gpointer user);
|
|
void on_trigger_toggled(GtkToggleButton* button, gpointer user);
|
|
void on_go_live(GtkButton*, gpointer user);
|
|
plot::u32 measure_color(std::size_t index);
|
|
bool visible_samples(App& app, std::size_t channel, std::vector<double>& time, std::vector<double>& value);
|
|
int measure_channel(App& app, const std::string& signal);
|
|
int measure_row(App& app, const std::string& signal);
|
|
std::string measure_unit(App& app, const std::string& signal);
|
|
GtkWidget* measures_row(App& app, std::size_t index, const Measure& m);
|
|
void rebuild_measures_pane(App& app);
|
|
void on_cursor_mode_changed(GObject* obj, GParamSpec*, gpointer user);
|
|
App::CursorLine cursors_line(App& app, const char* who, plot::Color chip, bool has_chip);
|
|
GtkWidget* cursors_header(App& app);
|
|
std::string row_unit(App& app, int row);
|
|
std::string slot_unit(App& app, int row, int slot);
|
|
std::uint32_t bound_slot_mask(App& app);
|
|
void rebuild_cursors_pane(App& app);
|
|
void update_cursors_pane(App& app);
|
|
GtkWidget* labelled_row(const char* label, GtkWidget* child, GtkWidget** label_out);
|
|
void measure_dialog_sync(MeasureDialog* d);
|
|
void on_measure_type_changed(GObject* obj, GParamSpec*, gpointer user);
|
|
void on_measure_dialog_apply(GtkButton*, gpointer user);
|
|
void on_measure_dialog_delete(GtkButton*, gpointer user);
|
|
void open_measure_dialog(App& app, int edit_index);
|
|
void place_measure_marks(App& app);
|
|
void place_search_marks(App& app);
|
|
void update_measures(App& app, bool force, bool accumulate);
|
|
void apply_math_channels(App& app);
|
|
void add_math_channel(App& app);
|
|
std::vector<std::string> math_source_names(App& app);
|
|
std::string math_dialog_problem(const MathDialog* d);
|
|
GdkRectangle math_node_rect(const MathNode& n);
|
|
void math_input_pos(const MathNode& n, int which, double& x, double& y);
|
|
void math_output_pos(const MathNode& n, double& x, double& y);
|
|
int math_node_at(const MathDialog* d, double x, double y);
|
|
int math_port_at(const MathDialog* d, double x, double y, double tol, int& which);
|
|
void math_to_graph(const MathDialog* d, double cx, double cy, double& gx, double& gy);
|
|
double math_set_scale(MathDialog* d, double want);
|
|
void math_zoom_at(MathDialog* d, double px, double py, double dy);
|
|
void math_view_reset(MathDialog* d);
|
|
void math_view_fit(MathDialog* d);
|
|
void math_tidy(MathDialog* d);
|
|
int math_port_at_canvas(const MathDialog* d, double cx, double cy, int& which);
|
|
int math_node_at_canvas(const MathDialog* d, double cx, double cy);
|
|
double math_seg_dist2(double px, double py, double ax, double ay, double bx, double by);
|
|
MathWireRef math_wire_at(const MathDialog* d, double gx, double gy, double tol);
|
|
MathWireRef math_wire_at_canvas(const MathDialog* d, double cx, double cy);
|
|
bool math_connect(MathDialog* d, int from, int to, int which, std::string* why);
|
|
void math_canvas_draw(GtkDrawingArea*, cairo_t* cr, int width, int height, gpointer user);
|
|
void math_canvas_pressed(GtkGestureClick*, int, double x, double y, gpointer user);
|
|
void math_canvas_drag_begin(GtkGestureDrag*, double x, double y, gpointer user);
|
|
void math_canvas_drag(GtkGestureDrag*, double dx, double dy, gpointer user);
|
|
void math_canvas_drag_end(GtkGestureDrag*, double dx, double dy, gpointer user);
|
|
gboolean math_canvas_scrolled(GtkEventControllerScroll* scroll, double, double dy, gpointer user);
|
|
gboolean math_canvas_key(GtkEventControllerKey*, guint keyval, guint, GdkModifierType state, gpointer user);
|
|
void math_add_node(MathDialog* d, MathOp op);
|
|
void on_math_node_added(GtkButton*, gpointer user);
|
|
void on_math_node_deleted(GtkButton*, gpointer user);
|
|
void on_math_output_set(GtkButton*, gpointer user);
|
|
void on_math_param_changed(GtkWidget*, gpointer user);
|
|
void on_math_source_changed(GObject*, GParamSpec*, gpointer user);
|
|
void on_math_op_changed(GObject* object, GParamSpec*, gpointer user);
|
|
void math_dialog_sync_form(MathDialog* d);
|
|
void math_dialog_sync(MathDialog* d);
|
|
void on_math_apply(GtkButton*, gpointer user);
|
|
GtkWidget* math_row(const char* label, GtkWidget* widget, GtkWidget** row_out);
|
|
void open_math_dialog(App& app, int edit);
|
|
GtkWidget* build_math_panel(App& app);
|
|
void rebuild_math_pane(App& app);
|
|
GtkWidget* build_measures_panel(App& app);
|
|
GtkWidget* build_cursors_panel(App& app);
|
|
void on_cursors_toggled(GtkToggleButton* button, gpointer user);
|
|
void on_signals_toggled(GtkToggleButton* button, gpointer user);
|
|
void on_fit_clicked(GtkButton*, gpointer user);
|
|
void on_fit_x_clicked(GtkButton*, gpointer user);
|
|
void on_cursors_reset(GtkButton*, gpointer user);
|
|
void on_export_response(GObject* source, GAsyncResult* result, gpointer user);
|
|
void on_export_clicked(GtkButton*, gpointer user);
|
|
void on_about_clicked(GtkButton*, gpointer user);
|
|
void on_help_clicked(GtkButton*, gpointer user);
|
|
void on_shortcuts_clicked(GtkButton*, gpointer user);
|
|
void on_y_auto_clicked(GtkButton* button, gpointer user);
|
|
void on_fixed_range_apply(GtkButton*, gpointer user);
|
|
void on_y_fixed_clicked(GtkButton* button, gpointer user);
|
|
void remove_trace(App& app, int row, int slot);
|
|
void on_remove_clicked(GtkButton* button, gpointer user);
|
|
GtkWidget* menu_icon_button(const char* icon, const char* label);
|
|
void on_row_menu_show_remove(GtkButton*, gpointer user);
|
|
void on_row_menu_back(GtkButton*, gpointer user);
|
|
void row_menu_fill(RowMenuState& st);
|
|
GtkWidget* open_row_menu(App& app, int row, double x, double y, GtkWidget* parent, bool autohide = true);
|
|
void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer user);
|
|
gboolean on_drop_channel(GtkDropTarget*, const GValue* value, double x, double y, gpointer user);
|
|
void update_side_visibility(App& app);
|
|
void on_sidebar_toggled(GtkToggleButton* button, gpointer user);
|
|
void rebuild_signal_panels(App& app);
|
|
void svg_declared_size(const char* data, gsize size, int* w, int* h);
|
|
std::string icon_report(GtkIconTheme* theme, const char* name, const char* resource);
|
|
void note_panel_controls(App& app);
|
|
void report_event_zoom(App& a, const char* what);
|
|
void update_status_popover(App& app, const udpscope::StatusSnapshot& s);
|
|
std::uint64_t capture_file_bytes(std::uint32_t slots);
|
|
std::string abbrev_home(const std::string& path);
|
|
void update_capture_tooltip(App& app);
|
|
void update_capture_row(App& app);
|
|
bool record_event(App& app);
|
|
bool replay_event(App& app, const EventRecord& record);
|
|
bool go_live_again(App& app);
|
|
bool read_event_samples(const std::string& path, EventSamples* out, std::string* error);
|
|
std::vector<EventRecord> selected_events(App& app);
|
|
void update_group_buttons(App& app);
|
|
bool build_event_group(App& app, const std::vector<EventRecord>& records, EventGroupKind kind, std::string* error);
|
|
void sync_compare_axes(App& app);
|
|
void on_group_clicked(GtkButton* button, gpointer user);
|
|
GdkTexture* scale_texture(GdkTexture* source, int width, int height);
|
|
EventThumbCache* event_thumb_cache(App& app);
|
|
GdkTexture* event_preview_texture(App& app, const std::string& path);
|
|
void refresh_events_pane(App& app);
|
|
void on_event_row_activated(GtkListBox*, GtkListBoxRow* row, gpointer user);
|
|
GtkWidget* build_events_pane(App& app);
|
|
gboolean tick(gpointer user);
|
|
void on_events_record_toggled(GObject* o, GParamSpec*, gpointer user);
|
|
GtkWidget* build_trigger_panel(App& app);
|
|
GtkWidget* build_signal_panel(App& app);
|
|
void note_starvation(App& app);
|
|
GtkWidget* build_window_control(App& app);
|
|
void run_app_action(App& app, AppAction action);
|
|
gboolean on_app_key_pressed(GtkEventControllerKey*, guint keyval, guint, GdkModifierType state, gpointer user);
|
|
GtkWidget* build_toolbar(App& app);
|
|
void on_activate(GtkApplication* application, gpointer user);
|
|
gboolean exit_probe_step3(gpointer user);
|
|
gboolean exit_probe_step2(gpointer user);
|
|
GtkWidget* find_labelled_button(GtkWidget* w, const char* label);
|
|
gboolean trig_trace_probe(gpointer user);
|
|
gboolean eventzoom_wait(gpointer user);
|
|
gboolean eventzoom_probe(gpointer user);
|
|
gboolean math_probe(gpointer user);
|
|
gboolean measure_probe(gpointer user);
|
|
gboolean measure_trace(gpointer user);
|
|
gboolean stats_probe_report(gpointer user);
|
|
gboolean stats_probe(gpointer user);
|
|
gboolean pan_probe(gpointer user);
|
|
gboolean arm_probe(gpointer user);
|
|
gboolean trigrenum_probe_step3(gpointer user);
|
|
gboolean trigrenum_probe_step2(gpointer user);
|
|
gboolean trigrenum_probe(gpointer user);
|
|
gboolean fields_probe(gpointer user);
|
|
gboolean condition_probe_report(gpointer user);
|
|
gboolean condition_probe_arm(gpointer user);
|
|
gboolean condition_probe(gpointer user);
|
|
gboolean search_probe_marks(gpointer user);
|
|
gboolean search_probe_report(gpointer user);
|
|
gboolean search_probe_run(gpointer user);
|
|
gboolean search_probe(gpointer user);
|
|
gboolean window_probe(gpointer user);
|
|
GtkWidget* layout_button(App& a, bool insert, int index);
|
|
void layout_probe_report(App& a, const char* what);
|
|
void layout_click(GtkWidget* b);
|
|
void layout_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean layout_probe_step(gpointer user);
|
|
gboolean enable_all_probe(gpointer user);
|
|
gboolean disarm_probe_step2(gpointer user);
|
|
gboolean disarm_probe_step3(gpointer user);
|
|
gboolean disarm_probe_wait(gpointer user);
|
|
gboolean disarm_probe_step1(gpointer user);
|
|
void pause_probe_report(App& a, const char* what);
|
|
gboolean pause_probe(gpointer user);
|
|
gboolean export_probe(gpointer user);
|
|
gboolean exit_probe_step1(gpointer user);
|
|
void cursors_probe_report(App& a, const char* what);
|
|
void fit_probe_report(App& a, const char* what);
|
|
gboolean timelabels_probe_step(gpointer user);
|
|
void fit_probe_zoom_in(App& a);
|
|
gboolean fit_probe_step(gpointer user);
|
|
void cursors_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean cursors_probe_step(gpointer user);
|
|
void measure_dialog_click(App& a, const char* label);
|
|
void measure_dialog_report(App& a, const char* what);
|
|
void measures_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean measures_probe_step(gpointer user);
|
|
gboolean zoomkeys_probe(gpointer user);
|
|
void menus_probe_next(App& a, int step, guint delay_ms);
|
|
void menu_row_labels(GtkWidget* box, GtkWidget** label, GtkWidget** accel);
|
|
gboolean menus_probe_step(gpointer user);
|
|
gboolean color_probe(gpointer user);
|
|
gboolean reseq_probe(gpointer user);
|
|
void capture_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean capture_probe_step(gpointer user);
|
|
gboolean configs_probe(gpointer user);
|
|
void rowmenu_probe_next(App& a, int step, guint delay_ms);
|
|
GtkWidget* row_menu_find(GtkWidget* container, const char* want);
|
|
void row_menu_measure(GtkWidget* box, const char* tag, int* rows_out, int* bad_out);
|
|
gboolean rowmenu_probe_step(gpointer user);
|
|
int signal_tick_visible(App& app);
|
|
int group_visible_traces(App& app, int row);
|
|
void report_events_availability(App& a, const char* what);
|
|
void events_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean events_probe_step(gpointer user);
|
|
gboolean events_group_probe_step(gpointer user);
|
|
void events_group_probe_next(App& a, int step, guint delay_ms);
|
|
gboolean drop_probe(gpointer user);
|
|
void collect_spins(GtkWidget* w, std::vector<double>* out);
|
|
gboolean defaults_probe(gpointer user);
|
|
gboolean keys_probe(gpointer user);
|
|
gboolean help_probe(gpointer user);
|
|
|
|
int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisition& acq,
|
|
RowSet& rows);
|
|
|
|
} // namespace ui_impl
|
|
} // namespace udpscope
|