udpscope: an oscilloscope view for a UDPStreamer source
GTK4 and libadwaita. It receives one UDP stream through the vendored udpsclient, buffers it in a large ring, and draws it with the vendored plotter: a trigger with a pre/post event window, a pause that freezes the window, cursors, measurements, math channels in a node editor, an event cache, named configurations, preferences, and an export of the visible window to .npy/.csv. Waveform files can be opened and analysed in place. The interface is two threads -- a producer that owns the socket and the GTK main thread that owns every widget and the GL context -- sharing one model under one mutex, with samples crossing through lock-free rings. deps/plotter is a submodule pinned to the commit this tree builds against; deps/udpsclient is vendored in place. docs/claude/ documents the rules that cost a defect to learn, and SPECS.md is the requirement document.
This commit is contained in:
1 parent
8f54841c2c
commit
0e256764d5
41 files changed
+2081
-154
No files matched your search
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
||||
//
|
||||
// The preferences form, without an owner.
|
||||
//
|
||||
// It used to be six widget fields on `App` and a function taking `App&`, which made it unreachable
|
||||
// from the one other place that needs it: the source chooser, which runs *before* `App` exists --
|
||||
// `main()` chooses a source, and only then builds the model and the UI. So the widgets and what they
|
||||
// contain are a struct the caller owns, and what a change *means* is a callback, because it means
|
||||
// different things on the two sides: the app follows its own switch and puts up a notice, the chooser
|
||||
// hands relative time to the run it is about to start.
|
||||
|
||||
#ifndef UDPSCOPE_UI_PREFS_HPP
|
||||
#define UDPSCOPE_UI_PREFS_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace udpscope {
|
||||
namespace ui_impl {
|
||||
|
||||
/// One preferences window's widgets, owned by whoever opened it.
|
||||
///
|
||||
/// Every pointer is valid only while `window` is non-null: the close path clears the lot, so a handler
|
||||
/// that fires after the window went away reads null rather than freed memory.
|
||||
struct PrefsForm {
|
||||
GtkWidget* window = nullptr;
|
||||
GtkWidget* dir_entry = nullptr;
|
||||
/// The folder chooser beside it. A text field alone asks a user to know a path and type it correctly;
|
||||
/// the chooser is how they find one, and it writes what it found into the same field -- so the text
|
||||
/// stays the single value, and there is nothing to keep in step.
|
||||
GtkWidget* browse = nullptr;
|
||||
GtkWidget* keep_spin = nullptr;
|
||||
GtkWidget* relative_switch = nullptr;
|
||||
GtkWidget* theme_drop = nullptr;
|
||||
GtkWidget* apply = nullptr;
|
||||
/// Shown only when a write fails. A preference that silently does not stick is worse than one that
|
||||
/// says so, and the two owners have nowhere in common to say it: the app has a status bar, the
|
||||
/// chooser does not.
|
||||
GtkWidget* status = nullptr;
|
||||
/// Called on *Apply*, with what the form held and whether it reached the file -- which is already
|
||||
/// written by the time this runs. Empty is allowed.
|
||||
std::function<void(const Settings&, bool written)> on_applied;
|
||||
};
|
||||
|
||||
/// Opens the preferences for `form`, transient for `parent` (which may be null), starting from
|
||||
/// `current`. Called while the window is up it raises that window instead of building a second one:
|
||||
/// two forms editing one file is how a user's change disappears.
|
||||
void prefs_open(PrefsForm& form, GtkWidget* parent, const Settings& current,
|
||||
std::function<void(const Settings&, bool written)> on_applied);
|
||||
|
||||
/// Closes it. Safe to call when it is not open, and safe to call from inside the apply callback.
|
||||
void prefs_close(PrefsForm& form);
|
||||
|
||||
/// Puts the interface into the named theme at once. Separate from the form because it is the one
|
||||
/// preference with nothing to plan: it applies to windows that are already on screen.
|
||||
void apply_theme(const std::string& name);
|
||||
|
||||
/// Fills every field the way a user would and presses *Apply*: the self-check's two entry points both
|
||||
/// end up here, so the app's probe and the chooser's probe cannot drift apart in what they exercise.
|
||||
void prefs_probe_fill_and_apply(PrefsForm& form);
|
||||
|
||||
} // namespace ui_impl
|
||||
} // namespace udpscope
|
||||
|
||||
#endif // UDPSCOPE_UI_PREFS_HPP
|
||||
Reference in new issue
Block a user