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.
89 lines
4.2 KiB
C++
89 lines
4.2 KiB
C++
// SPDX-License-Identifier: MIT
|
|
#include "shortcuts.hpp"
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
namespace udpscope {
|
|
namespace {
|
|
|
|
/// Bare letters are toggles, Ctrl is for files and windows. Nothing here shadows a key the
|
|
/// plot's own controller handles ('+', '-', 'f', the arrows, Escape).
|
|
constexpr Accel kAccels[] = {
|
|
{GDK_KEY_space, 0, AppAction::PauseToggle, "space", "Pause or resume the display",
|
|
"Acquisition"},
|
|
{GDK_KEY_a, 0, AppAction::TriggerArm, "a", "Arm or disarm the trigger", "Acquisition"},
|
|
{GDK_KEY_t, 0, AppAction::TriggerPanel, "t", "Show or hide the trigger panel",
|
|
"Acquisition"},
|
|
{GDK_KEY_c, 0, AppAction::CursorsPane, "c", "Show or hide the cursors pane",
|
|
"Panes and editors"},
|
|
{GDK_KEY_m, 0, AppAction::MeasuresPane, "m", "Show or hide the measurements pane",
|
|
"Panes and editors"},
|
|
{GDK_KEY_g, 0, AppAction::MathPane, "g", "Show or hide the math pane",
|
|
"Panes and editors"},
|
|
{GDK_KEY_l, 0, AppAction::LayoutEditor, "l", "Show or hide the layout editor",
|
|
"Panes and editors"},
|
|
{GDK_KEY_s, 0, AppAction::SidePanel, "s", "Show or hide the side panel",
|
|
"Panes and editors"},
|
|
{GDK_KEY_e, GDK_CONTROL_MASK, AppAction::Export, "<Control>e",
|
|
"Export the visible range", "Files and windows"},
|
|
{GDK_KEY_F1, 0, AppAction::Help, "F1", "Open the help window", "Files and windows"},
|
|
{GDK_KEY_slash, GDK_CONTROL_MASK, AppAction::Shortcuts, "<Control>slash",
|
|
"Open this shortcut overview", "Files and windows"},
|
|
{GDK_KEY_q, GDK_CONTROL_MASK, AppAction::Quit, "<Control>q", "Quit udpscope",
|
|
"Files and windows"},
|
|
};
|
|
constexpr int kAccelCount = static_cast<int>(sizeof(kAccels) / sizeof(kAccels[0]));
|
|
|
|
/// The plot's own interactions, documentation only. The keyboard's modifiers are not the
|
|
/// wheel's: '+' is shift+'=' on common layouts, so Shift could not mean anything there.
|
|
///
|
|
/// A space-separated accelerator is the two-key alternative form the shortcut label draws
|
|
/// as `+ / -`. The mouse rows carry no accelerator at all: neither GTK's accelerator syntax nor
|
|
/// libadwaita's knows a wheel or a drag, and asking either to parse "Wheel" is a parse warning and an
|
|
/// empty cell. The overview draws them from `src/ui/shortcuts.ui`, which gives each one GTK's generic
|
|
/// gesture type and an icon where the keycaps would be. That file is the *presentation*; this table is
|
|
/// what the application handles, which is why `[help]`'s `ui=` is checked against `tables=`.
|
|
constexpr Gesture kGestures[] = {
|
|
{"plus minus", "", "Zoom in or out, both axes", "Plot keyboard"},
|
|
{"<Control>plus <Control>minus", "", "Zoom the time axis only", "Plot keyboard"},
|
|
{"<Alt>plus <Alt>minus", "", "Zoom the value axis only", "Plot keyboard"},
|
|
{"Left Right", "", "Pan along time", "Plot keyboard"},
|
|
{"Up Down", "", "Pan along values", "Plot keyboard"},
|
|
{"f", "", "Fit the view to the data", "Plot keyboard"},
|
|
{"Escape", "", "Cancel the gesture in progress", "Plot keyboard"},
|
|
{"", "Wheel", "Zoom about the pointer, both axes", "Plot mouse"},
|
|
{"", "Shift and wheel", "Zoom the time axis only", "Plot mouse"},
|
|
{"", "Ctrl and wheel", "Zoom the value axis only", "Plot mouse"},
|
|
{"", "Drag", "Pan", "Plot mouse"},
|
|
{"", "Ctrl and drag", "Box zoom on time", "Plot mouse"},
|
|
{"", "Alt and drag", "Box zoom on values", "Plot mouse"},
|
|
{"", "Shift and drag", "Box zoom on time and values", "Plot mouse"},
|
|
};
|
|
constexpr int kGestureCount = static_cast<int>(sizeof(kGestures) / sizeof(kGestures[0]));
|
|
|
|
/// The only modifiers a table row may require. Lock, Num-Lock and the pointer buttons all
|
|
/// arrive in the same mask and must be masked out or no row ever matches.
|
|
constexpr unsigned kModMask = GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_ALT_MASK;
|
|
|
|
} // namespace
|
|
|
|
const Accel* app_accels(int* count) {
|
|
if (count != nullptr) *count = kAccelCount;
|
|
return kAccels;
|
|
}
|
|
|
|
const Gesture* plot_gestures(int* count) {
|
|
if (count != nullptr) *count = kGestureCount;
|
|
return kGestures;
|
|
}
|
|
|
|
const Accel* find_accel(unsigned keyval, unsigned mods) {
|
|
const unsigned wanted = mods & kModMask;
|
|
for (const Accel& accel : kAccels) {
|
|
if (accel.keyval == keyval && accel.mods == wanted) return &accel;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace udpscope
|