ui: the source chooser's window is a .ui file, and its pages an AdwViewStack
The last hand-built UI outside ui.cpp. The window, its header bar, its two pages and the four buttons that are about the chooser rather than about a source now live in src/ui/source-chooser.ui; the rows stay in code because a source row and a waveform row are data, rebuilt whenever the known hosts or the recent files change. The pages were a GtkNotebook and are an AdwViewStack under an AdwViewSwitcherBar, which is what current libadwaita asks for and the same information without a tab strip. The three actions moved from a button row at the foot into the header bar, the shape source-form.ui already uses -- and deliberately without receives-default, since Enter must keep activating the selected row. Nothing carries a value: the tooltips and labels moved as they were, the handlers are the same functions, and the switch is by page *name* rather than by index. The probe reads the frame back -- `[sources] chooser ui: page='sources' title='Sources' icon=yes page='waveforms' … switcher_wired=1 buttons=[prefs=1 new_host=1 open=1 cancel=1]` -- so a .ui that lost an id is named instead of opening a window with a hole in it, and `check_ui` type-checks the file against the GIR (7 ok, 0 failed). Two things the checks caught while doing it: the new frame dropped the call that fills the waveforms page (the unused-function warning), and `AdwViewStackPage` is a GObject that *wraps* a child rather than being one, so reading the pages needs adw_view_stack_get_page().
This commit is contained in:
1 parent
723f5c3b4c
commit
0a64e9e854
5 files changed
+216
-80
No files matched your search
@@ -945,14 +945,16 @@ configuration that carries settings, is the defect this exists for — a saved h
|
||||
## `[sources] waveforms` and the waveform load
|
||||
|
||||
```
|
||||
[sources] waveforms: 1 recent /tmp/rt.npy(ok) page=1
|
||||
[sources] chooser ui: page='sources' title='Sources' icon=yes page='waveforms' title='Waveforms' icon=yes visible='sources' switcher_wired=1 buttons=[prefs=1 new_host=1 open=1 cancel=1]
|
||||
[sources] waveforms: 1 recent /tmp/rt.npy(ok) page=waveforms
|
||||
[sources] picked waveform /tmp/rt.npy
|
||||
[udpscope] waveform loaded: /tmp/rt.npy signals=4 samples=20001 span=2.000000s rate=10000 time=[-2.000000..0.000000]
|
||||
```
|
||||
|
||||
| field | value it must read | the defect a wrong reading names |
|
||||
| --- | --- | --- |
|
||||
| `waveforms:` | one entry per remembered export, with `(ok)` or `(missing)`, and `page=1` | the Waveforms page not built, or a stale path offered as if it were openable |
|
||||
| `chooser ui:` | both pages with a `title` and an `icon=yes`, `switcher_wired=1`, and all four buttons `1` | the chooser's `.ui` losing an id: the code names the missing one and refuses the window, and this line is the frame that *did* build -- a page with `icon=NO` is one the switcher cannot draw |
|
||||
| `waveforms:` | one entry per remembered export, with `(ok)` or `(missing)`, and `page=waveforms` once the page is shown | the Waveforms page not built, or a stale path offered as if it were openable |
|
||||
| `picked waveform` | the selected path | a pick that carries no file: `--open` is what the mode reads |
|
||||
| `signals=`, `samples=` | the export's `cols=` and `rows=` | a reader mis-slicing its records, or the columns bound to the wrong channels |
|
||||
| `span=`, `rate=`, `time=[…]` | the exported window and rate, `[-window, 0]` | the plan not covering the whole file (the tail arrives as the whole picture) or the display epoch not pinned to the file's last stamp |
|
||||
|
||||
@@ -343,7 +343,7 @@ as distinct from the fault injection below, which exercises ingest:
|
||||
reads as one that cannot plot anything. A file-mode run must also write **no session** for the
|
||||
default `127.0.0.1:8000` (`App::source_port` is 0), or it overwrites that source's saved layout.
|
||||
- **The chooser's Waveforms page**: `UDPSCOPE_PROBE_WAVEFORMS=1` prints
|
||||
`[sources] waveforms: N recent <path>(ok|missing) … page=1`, and `UDPSCOPE_PROBE_WAVE=<path>` sets
|
||||
`[sources] waveforms: N recent <path>(ok|missing) … page=waveforms`, and `UDPSCOPE_PROBE_WAVE=<path>` sets
|
||||
the field the file dialog's callback sets and prints `[sources] picked waveform <path>` — then the
|
||||
load line above, with no socket involved.
|
||||
- **A `--server` launch reads the stored configuration.** With a throwaway `XDG_CONFIG_HOME`
|
||||
|
||||
+91
-77
@@ -69,10 +69,25 @@ struct Chooser {
|
||||
GtkWidget* window = nullptr;
|
||||
GtkWidget* list = nullptr;
|
||||
std::vector<KnownSource> known;
|
||||
/// The Waveforms page: the exports opened before, newest first, and the notebook that holds the
|
||||
/// two pages. A waveform is the *other* answer to "which source", so it gets a page of its own
|
||||
/// rather than a button that hides a file dialog behind one more click.
|
||||
GtkWidget* notebook = nullptr;
|
||||
/// The window's frame is a `.ui` file (`src/ui/source-chooser.ui`): the window, its header bar, the two
|
||||
/// pages and the buttons that are about the chooser rather than about a source. The builder is held
|
||||
/// with the window for the same reason the form's is -- the window belongs to the builder's object list.
|
||||
GtkBuilder* ui = nullptr;
|
||||
/// The Waveforms page: the exports opened before, newest first, and the stack that holds the two pages.
|
||||
/// A waveform is the *other* answer to "which source", so it gets a page of its own rather than a button
|
||||
/// that hides a file dialog behind one more click. A `GtkNotebook` was the first shape of this and a tab
|
||||
/// strip is the wrong one: an `AdwViewStack` under an `AdwViewSwitcherBar` is what current libadwaita
|
||||
/// asks for, and the switcher's stack is wired in code because a property cannot name a widget declared
|
||||
/// elsewhere in the same tree.
|
||||
GtkWidget* page_stack = nullptr;
|
||||
GtkWidget* switcher = nullptr;
|
||||
/// The buttons the header bar holds, kept because two of them are read at runtime: `new_host` takes the
|
||||
/// focus when the list is empty (there is nothing else to do), and all of them are read back by
|
||||
/// `report_chooser_ui()` so a `.ui` that lost one is named rather than silently missing its button.
|
||||
GtkWidget* prefs_button = nullptr;
|
||||
GtkWidget* new_host = nullptr;
|
||||
GtkWidget* waves_open = nullptr;
|
||||
GtkWidget* cancel_button = nullptr;
|
||||
GtkWidget* wave_list = nullptr;
|
||||
std::vector<std::string> waves;
|
||||
/// The connection form, created on demand. Members of the chooser rather than a heap struct:
|
||||
@@ -136,6 +151,9 @@ void chooser_finish(Chooser& c, bool chosen) {
|
||||
c.window = nullptr; // before the destroy: `close-request` must not re-enter this
|
||||
gtk_window_destroy(GTK_WINDOW(w));
|
||||
}
|
||||
// The window belonged to the builder's object list, so it is freed with it -- after the destroy, never
|
||||
// before: a builder freed under a live window takes the window's children with it.
|
||||
g_clear_object(&c.ui);
|
||||
if (c.loop != nullptr) g_main_loop_quit(c.loop);
|
||||
}
|
||||
|
||||
@@ -582,6 +600,34 @@ void open_chooser_preferences(Chooser& c) {
|
||||
/// `UDPSCOPE_PROBE_NEWHOST=host:port` drives the form instead of a row. The crash surface of a
|
||||
/// dialog built, filled and destroyed is as much the point as the values, the same way
|
||||
/// `UDPSCOPE_PROBE_FIELDS_MS` is in `ui.cpp`.
|
||||
/// The chooser's frame, read back from the `.ui` it was built from: the two pages by name and title, the
|
||||
/// switcher wired to the stack, and the four buttons the code resolved. `[help] ui=` does the same against
|
||||
/// its tables -- the frame is what the file holds, so the frame is what is read, while the rows a user sees
|
||||
/// are read by the probes that drive them.
|
||||
void report_chooser_ui(const Chooser& c) {
|
||||
std::fprintf(stderr, "[sources] chooser ui:");
|
||||
for (GtkWidget* w = gtk_widget_get_first_child(c.page_stack); w != nullptr;
|
||||
w = gtk_widget_get_next_sibling(w)) {
|
||||
// A page is not the child: `AdwViewStackPage` is a `GObject` that *wraps* one, so the stack is what
|
||||
// turns a child back into its page.
|
||||
AdwViewStackPage* page = adw_view_stack_get_page(ADW_VIEW_STACK(c.page_stack), w);
|
||||
if (page == nullptr) continue;
|
||||
std::fprintf(stderr, " page='%s' title='%s' icon=%s", adw_view_stack_page_get_name(page),
|
||||
adw_view_stack_page_get_title(page),
|
||||
adw_view_stack_page_get_icon_name(page) != nullptr ? "yes" : "NO");
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
" visible='%s' switcher_wired=%d buttons=[prefs=%d new_host=%d open=%d cancel=%d]\n",
|
||||
adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(c.page_stack)),
|
||||
adw_view_switcher_bar_get_stack(ADW_VIEW_SWITCHER_BAR(c.switcher)) ==
|
||||
ADW_VIEW_STACK(c.page_stack)
|
||||
? 1
|
||||
: 0,
|
||||
c.prefs_button != nullptr ? 1 : 0, c.new_host != nullptr ? 1 : 0,
|
||||
c.waves_open != nullptr ? 1 : 0, c.cancel_button != nullptr ? 1 : 0);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
gboolean pick_probe(gpointer user) {
|
||||
Chooser& c = *static_cast<Chooser*>(user);
|
||||
// Every path below returns `G_SOURCE_REMOVE`, so the source is gone the moment this returns and
|
||||
@@ -589,6 +635,9 @@ gboolean pick_probe(gpointer user) {
|
||||
// -probe call would otherwise be a GLib critical on every run that let the probe fire.
|
||||
c.pick_source = 0;
|
||||
|
||||
// What the `.ui` contributed, before anything is driven: the pages, the switcher and the buttons.
|
||||
report_chooser_ui(c);
|
||||
|
||||
// `UDPSCOPE_PROBE_CHOOSER_PREFS=1`: the preferences opened from *here* rather than from the app's
|
||||
// menu -- the entry point that exists because this dialog runs before `App`. It is driven through
|
||||
// the same fill-and-apply the app's probe uses, and reports what the run about to start will get,
|
||||
@@ -611,13 +660,13 @@ gboolean pick_probe(gpointer user) {
|
||||
// page selected the way a click on its tab does. `UDPSCOPE_PROBE_WAVE=<path>` is the file dialog's
|
||||
// answer without the dialog -- the same field, set the same way.
|
||||
if (g_getenv("UDPSCOPE_PROBE_WAVEFORMS") != nullptr) {
|
||||
if (c.notebook != nullptr) gtk_notebook_set_current_page(GTK_NOTEBOOK(c.notebook), 1);
|
||||
adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(c.page_stack), "waveforms");
|
||||
std::fprintf(stderr, "[sources] waveforms: %zu recent", c.waves.size());
|
||||
for (const std::string& p : c.waves) {
|
||||
std::fprintf(stderr, " %s(%s)", p.c_str(), ::access(p.c_str(), R_OK) == 0 ? "ok" : "missing");
|
||||
}
|
||||
std::fprintf(stderr, " page=%d\n",
|
||||
static_cast<int>(gtk_notebook_get_current_page(GTK_NOTEBOOK(c.notebook))));
|
||||
std::fprintf(stderr, " page=%s\n",
|
||||
adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(c.page_stack)));
|
||||
std::fflush(stderr);
|
||||
}
|
||||
if (const char* path = g_getenv("UDPSCOPE_PROBE_WAVE")) {
|
||||
@@ -762,88 +811,53 @@ bool choose_source(Config& cfg) {
|
||||
|
||||
Chooser c;
|
||||
c.cfg = &cfg;
|
||||
c.window = gtk_window_new();
|
||||
gtk_window_set_title(GTK_WINDOW(c.window), "udpscope \xe2\x80\x94 choose a source");
|
||||
gtk_window_set_default_size(GTK_WINDOW(c.window), 420, 320);
|
||||
|
||||
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_window_set_child(GTK_WINDOW(c.window), box);
|
||||
// The frame is a `.ui` file; the rows are not, because a source row and a waveform row are data and are
|
||||
// rebuilt whenever the known hosts or the recent files change. `make test-ui` checks the file's types
|
||||
// against the GIR; this checks that the code and the file still agree on the names, which nothing else
|
||||
// can -- a missing id would otherwise open a window with a hole in it, or crash on the first read.
|
||||
c.ui = gtk_builder_new_from_resource("/org/udpscope/ui/source-chooser.ui");
|
||||
c.window = GTK_WIDGET(gtk_builder_get_object(c.ui, "chooser_window"));
|
||||
c.page_stack = GTK_WIDGET(gtk_builder_get_object(c.ui, "page_stack"));
|
||||
c.switcher = GTK_WIDGET(gtk_builder_get_object(c.ui, "page_switcher"));
|
||||
c.list = GTK_WIDGET(gtk_builder_get_object(c.ui, "sources_list"));
|
||||
c.wave_list = GTK_WIDGET(gtk_builder_get_object(c.ui, "waves_list"));
|
||||
c.prefs_button = GTK_WIDGET(gtk_builder_get_object(c.ui, "prefs_button"));
|
||||
c.new_host = GTK_WIDGET(gtk_builder_get_object(c.ui, "new_host_button"));
|
||||
c.waves_open = GTK_WIDGET(gtk_builder_get_object(c.ui, "waves_open"));
|
||||
c.cancel_button = GTK_WIDGET(gtk_builder_get_object(c.ui, "cancel_button"));
|
||||
if (c.window == nullptr || c.page_stack == nullptr || c.switcher == nullptr || c.list == nullptr ||
|
||||
c.wave_list == nullptr || c.prefs_button == nullptr || c.new_host == nullptr ||
|
||||
c.waves_open == nullptr || c.cancel_button == nullptr) {
|
||||
std::fprintf(stderr, "[sources] chooser ui: source-chooser.ui is missing an id this code reads\n");
|
||||
std::fflush(stderr);
|
||||
return false;
|
||||
}
|
||||
adw_view_switcher_bar_set_stack(ADW_VIEW_SWITCHER_BAR(c.switcher), ADW_VIEW_STACK(c.page_stack));
|
||||
|
||||
// Two pages under one window: the sources it can connect to, and the waveforms it can open. A
|
||||
// waveform is not a source with a file in place of an address -- there is no socket and nothing
|
||||
// arrives -- so it is a *page* rather than a field, which is also where its history belongs.
|
||||
c.notebook = gtk_notebook_new();
|
||||
gtk_widget_set_vexpand(c.notebook, TRUE);
|
||||
gtk_box_append(GTK_BOX(box), c.notebook);
|
||||
|
||||
GtkWidget* scroller = gtk_scrolled_window_new();
|
||||
gtk_widget_set_vexpand(scroller, TRUE);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller), GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
c.list = gtk_list_box_new();
|
||||
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(c.list), TRUE);
|
||||
g_signal_connect(c.list, "row-activated", G_CALLBACK(on_row_activated), &c);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroller), c.list);
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(c.notebook), scroller, gtk_label_new("Sources"));
|
||||
|
||||
GtkWidget* wave_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
GtkWidget* wave_scroller = gtk_scrolled_window_new();
|
||||
gtk_widget_set_vexpand(wave_scroller, TRUE);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(wave_scroller), GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
c.wave_list = gtk_list_box_new();
|
||||
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(c.wave_list), TRUE);
|
||||
g_signal_connect(c.wave_list, "row-activated", G_CALLBACK(on_wave_activated), &c);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(wave_scroller), c.wave_list);
|
||||
gtk_box_append(GTK_BOX(wave_page), wave_scroller);
|
||||
GtkWidget* wave_actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
gtk_widget_set_halign(wave_actions, GTK_ALIGN_END);
|
||||
gtk_widget_set_margin_top(wave_actions, 6);
|
||||
gtk_widget_set_margin_bottom(wave_actions, 6);
|
||||
gtk_widget_set_margin_end(wave_actions, 12);
|
||||
GtkWidget* wave_open = gtk_button_new_with_label("Open\xe2\x80\xa6");
|
||||
gtk_widget_set_tooltip_text(wave_open, "choose an exported .npy to view and analyse");
|
||||
g_signal_connect(wave_open, "clicked", G_CALLBACK(on_wave_open_clicked), &c);
|
||||
gtk_box_append(GTK_BOX(wave_actions), wave_open);
|
||||
gtk_box_append(GTK_BOX(wave_page), wave_actions);
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(c.notebook), wave_page, gtk_label_new("Waveforms"));
|
||||
chooser_populate_waves(c);
|
||||
|
||||
GtkWidget* actions = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
gtk_widget_set_margin_start(actions, 12);
|
||||
gtk_widget_set_margin_end(actions, 12);
|
||||
gtk_widget_set_margin_top(actions, 12);
|
||||
gtk_widget_set_margin_bottom(actions, 12);
|
||||
gtk_widget_set_halign(actions, GTK_ALIGN_END);
|
||||
// The preferences are reachable from here as well as from the main window's menu: this dialog is
|
||||
// the only window that exists before anything is planned, so it is where a user still has the
|
||||
// chance to say how this run should behave.
|
||||
GtkWidget* prefs = gtk_button_new_with_label("Preferences\xe2\x80\xa6");
|
||||
gtk_widget_set_tooltip_text(prefs,
|
||||
"the event cache, relative time and the interface's theme -- the same "
|
||||
"dialog as the main window's menu, and the only place relative time can "
|
||||
"still change this run");
|
||||
g_signal_connect(prefs, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
open_chooser_preferences(*static_cast<Chooser*>(user));
|
||||
}),
|
||||
&c);
|
||||
GtkWidget* new_host = gtk_button_new_with_label("New host\xe2\x80\xa6");
|
||||
g_signal_connect(new_host, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
g_signal_connect(c.waves_open, "clicked", G_CALLBACK(on_wave_open_clicked), &c);
|
||||
g_signal_connect(c.new_host, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
open_form(*static_cast<Chooser*>(user));
|
||||
}),
|
||||
&c);
|
||||
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
|
||||
g_signal_connect(cancel, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
g_signal_connect(c.cancel_button, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
chooser_finish(*static_cast<Chooser*>(user), false);
|
||||
}),
|
||||
&c);
|
||||
gtk_box_append(GTK_BOX(actions), prefs);
|
||||
gtk_box_append(GTK_BOX(actions), new_host);
|
||||
gtk_box_append(GTK_BOX(actions), cancel);
|
||||
gtk_box_append(GTK_BOX(box), actions);
|
||||
// The preferences are reachable from here as well as from the main window's menu: this dialog is the
|
||||
// only window that exists before anything is planned, so it is where a user still has the chance to say
|
||||
// how this run should behave.
|
||||
g_signal_connect(c.prefs_button, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||
open_chooser_preferences(*static_cast<Chooser*>(user));
|
||||
}),
|
||||
&c);
|
||||
|
||||
chooser_populate(c);
|
||||
if (c.known.empty()) gtk_widget_grab_focus(new_host);
|
||||
// The other page's rows too: fills the sources, and this is the pair of it.
|
||||
chooser_populate_waves(c);
|
||||
if (c.known.empty()) gtk_widget_grab_focus(c.new_host);
|
||||
|
||||
g_signal_connect(c.window, "close-request", G_CALLBACK(on_chooser_close), &c);
|
||||
GtkEventController* keys = gtk_event_controller_key_new();
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
What to watch: one page of sources it can connect to, one page of waveforms opened before, and the
|
||||
three buttons that are about the chooser rather than about a source.
|
||||
|
||||
This window exists *before* the application does. `main()` asks it what to open and only then builds
|
||||
the model and the rest of the interface, which is why the preferences are reachable from here at all
|
||||
— and why nothing in this file may reach for `App`.
|
||||
|
||||
The two pages are an `AdwViewStack` under an `AdwViewSwitcherBar` and not a `GtkNotebook`: a tab strip
|
||||
is the wrong shape for two views of one thing, and the switcher is what current libadwaita asks for.
|
||||
The bar is revealed unconditionally because this window is small enough that it always is narrow.
|
||||
|
||||
The rows are not here, deliberately. A source row and a waveform row are *data* — `source_dialog.cpp`
|
||||
builds them from the known hosts and the recent files, and rebuilds them whenever either changes — so
|
||||
this file holds the frame and the fixed buttons, and nothing here carries a value.
|
||||
-->
|
||||
<interface>
|
||||
<object class="AdwWindow" id="chooser_window">
|
||||
<property name="title" translatable="yes">udpscope — choose a source</property>
|
||||
<property name="default-width">420</property>
|
||||
<property name="default-height">360</property>
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="prefs_button">
|
||||
<property name="label" translatable="yes">_Preferences…</property>
|
||||
<property name="use-underline">1</property>
|
||||
<property name="tooltip-text" translatable="yes">the event cache, relative time and the interface's theme — the same dialog as the main window's menu, and the only place relative time can still change this run</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label" translatable="yes">_Cancel</property>
|
||||
<property name="use-underline">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="new_host_button">
|
||||
<property name="label" translatable="yes">_New host…</property>
|
||||
<property name="use-underline">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwViewStack" id="page_stack">
|
||||
<child>
|
||||
<object class="AdwViewStackPage">
|
||||
<property name="name">sources</property>
|
||||
<property name="title" translatable="yes">Sources</property>
|
||||
<property name="icon-name">network-transmit-receive-symbolic</property>
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox" id="sources_list">
|
||||
<property name="activate-on-single-click">1</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwViewStackPage">
|
||||
<property name="name">waveforms</property>
|
||||
<property name="title" translatable="yes">Waveforms</property>
|
||||
<property name="icon-name">document-open-recent-symbolic</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="vexpand">1</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="child">
|
||||
<object class="GtkListBox" id="waves_list">
|
||||
<property name="activate-on-single-click">1</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="margin-end">12</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="waves_open">
|
||||
<property name="label" translatable="yes">_Open…</property>
|
||||
<property name="use-underline">1</property>
|
||||
<property name="tooltip-text" translatable="yes">choose an exported .npy to view and analyse</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<child type="bottom">
|
||||
<object class="AdwViewSwitcherBar" id="page_switcher">
|
||||
<property name="reveal">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@@ -13,6 +13,7 @@
|
||||
<file preprocess="xml-stripblanks">preferences.ui</file>
|
||||
<file preprocess="xml-stripblanks">help.ui</file>
|
||||
<file preprocess="xml-stripblanks">source-form.ui</file>
|
||||
<file preprocess="xml-stripblanks">source-chooser.ui</file>
|
||||
<file preprocess="xml-stripblanks">main-window.ui</file>
|
||||
<file preprocess="xml-stripblanks">cursors-panel.ui</file>
|
||||
</gresource>
|
||||
|
||||
Reference in new issue
Block a user