e1608: a Scan network row in the connection form, with click-to-fill results
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
43c21a0cc2
commit
24bdd6b930
1 file changed
+133
-4
@@ -11,6 +11,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace udpscope {
|
||||
namespace {
|
||||
@@ -82,6 +83,11 @@ struct Form {
|
||||
ChangeCb changed = std::make_shared<std::function<void()>>();
|
||||
bool updating = false;
|
||||
bool alive = true; ///< cleared when the chooser destroys the rows; handlers then touch nothing
|
||||
GtkWidget* scan_row = nullptr; ///< the "Scan network" expander; the results are its rows
|
||||
std::vector<GtkWidget*> results; ///< the result rows of the current scan
|
||||
std::unique_ptr<Scan> scan; ///< non-null while a scan is running
|
||||
guint timer = 0; ///< the GLib source polling `scan`, 0 when none
|
||||
int scan_ms = 0; ///< time spent in the running scan
|
||||
|
||||
bool diff() const { return sel_of(mode) == 1; }
|
||||
int enabled_ai() const {
|
||||
@@ -164,6 +170,24 @@ struct Form {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/// A result click: puts one way of naming the board in and the other two fields out, under the
|
||||
/// `updating` guard so the dialog is told once.
|
||||
void fill(const std::string& addr, const std::string& mac_text) {
|
||||
if (!alive) return;
|
||||
updating = true;
|
||||
gtk_editable_set_text(GTK_EDITABLE(address), addr.c_str());
|
||||
gtk_editable_set_text(GTK_EDITABLE(mac), mac_text.c_str());
|
||||
gtk_editable_set_text(GTK_EDITABLE(subnet), "");
|
||||
updating = false;
|
||||
if (changed && *changed) (*changed)();
|
||||
}
|
||||
|
||||
void stop_scan() {
|
||||
if (timer != 0) g_source_remove(timer);
|
||||
timer = 0;
|
||||
scan.reset();
|
||||
}
|
||||
|
||||
bool valid() const { return enabled_ai() > 0 && E1608Settings::from(read()).problem().empty(); }
|
||||
};
|
||||
|
||||
@@ -185,7 +209,11 @@ void on_edited(GtkEditable*, gpointer user) {
|
||||
void free_form(gpointer d, GClosure*) { delete static_cast<FormPtr*>(d); }
|
||||
|
||||
/// "destroy" on a row group: the siblings the handlers reach by raw pointer may already be gone.
|
||||
void on_form_destroy(GtkWidget*, gpointer user) { (**static_cast<FormPtr*>(user)).alive = false; }
|
||||
void on_form_destroy(GtkWidget*, gpointer user) {
|
||||
Form& f = **static_cast<FormPtr*>(user);
|
||||
f.alive = false;
|
||||
f.stop_scan(); // the poll must not outlive the rows it writes to
|
||||
}
|
||||
|
||||
void hook_destroy(GtkWidget* w, const FormPtr& f) {
|
||||
g_signal_connect_data(w, "destroy", G_CALLBACK(on_form_destroy), new FormPtr(f), free_form,
|
||||
@@ -202,6 +230,96 @@ void hook_entry(GtkWidget* w, const FormPtr& f) {
|
||||
static_cast<GConnectFlags>(0));
|
||||
}
|
||||
|
||||
constexpr int kScanMs = 1500; ///< how long a scan listens
|
||||
constexpr int kTickMs = 50; ///< how often it drains the sockets
|
||||
|
||||
/// A result row's two ways to name the board, with the form they fill.
|
||||
struct Pick {
|
||||
FormPtr form;
|
||||
std::string ip, mac;
|
||||
};
|
||||
|
||||
void free_pick(gpointer d, GClosure*) { delete static_cast<Pick*>(d); }
|
||||
|
||||
void on_pick_address(AdwActionRow*, gpointer user) {
|
||||
Pick& p = *static_cast<Pick*>(user);
|
||||
p.form->fill(p.ip, "");
|
||||
}
|
||||
|
||||
void on_pick_mac(GtkButton*, gpointer user) {
|
||||
Pick& p = *static_cast<Pick*>(user);
|
||||
p.form->fill("", p.mac);
|
||||
}
|
||||
|
||||
void add_result(const FormPtr& f, const DeviceInfo& d) {
|
||||
const std::string mac = format_mac(d.mac);
|
||||
GtkWidget* row = adw_action_row_new();
|
||||
gtk_widget_set_name(row, "e1608-result");
|
||||
adw_preferences_row_set_title(ADW_PREFERENCES_ROW(row), d.ip.c_str());
|
||||
adw_action_row_set_subtitle(ADW_ACTION_ROW(row), mac.c_str());
|
||||
gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(row), TRUE);
|
||||
GtkWidget* use_mac = gtk_button_new_with_label("Use MAC");
|
||||
gtk_widget_set_name(use_mac, "e1608-use-mac");
|
||||
gtk_widget_set_valign(use_mac, GTK_ALIGN_CENTER);
|
||||
adw_action_row_add_suffix(ADW_ACTION_ROW(row), use_mac);
|
||||
g_signal_connect_data(row, "activated", G_CALLBACK(on_pick_address),
|
||||
new Pick{f, d.ip, mac}, free_pick, static_cast<GConnectFlags>(0));
|
||||
g_signal_connect_data(use_mac, "clicked", G_CALLBACK(on_pick_mac), new Pick{f, d.ip, mac},
|
||||
free_pick, static_cast<GConnectFlags>(0));
|
||||
adw_expander_row_add_row(ADW_EXPANDER_ROW(f->scan_row), row);
|
||||
adw_expander_row_set_expanded(ADW_EXPANDER_ROW(f->scan_row), TRUE);
|
||||
f->results.push_back(row);
|
||||
}
|
||||
|
||||
void set_scan_status(Form& f, const std::string& text) {
|
||||
adw_expander_row_set_subtitle(ADW_EXPANDER_ROW(f.scan_row), text.c_str());
|
||||
}
|
||||
|
||||
/// 50 ms poll of the running scan. The rows appear as boards answer; at `kScanMs` the status is
|
||||
/// settled and the scan (its sockets) is released.
|
||||
gboolean on_scan_tick(gpointer user) {
|
||||
const FormPtr& fp = *static_cast<FormPtr*>(user);
|
||||
Form& f = *fp;
|
||||
if (!f.alive || !f.scan) {
|
||||
f.timer = 0;
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
const std::vector<DeviceInfo> found = f.scan->poll();
|
||||
while (f.results.size() < found.size()) add_result(fp, found[f.results.size()]);
|
||||
f.scan_ms += kTickMs;
|
||||
if (f.scan_ms < kScanMs) return G_SOURCE_CONTINUE;
|
||||
if (found.empty())
|
||||
set_scan_status(f, "No board found");
|
||||
else if (found.size() == 1)
|
||||
set_scan_status(f, "1 board found");
|
||||
else
|
||||
set_scan_status(f, std::to_string(found.size()) + " boards found");
|
||||
f.scan.reset();
|
||||
f.timer = 0;
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void free_form_ptr(gpointer d) { delete static_cast<FormPtr*>(d); }
|
||||
|
||||
/// A new scan on the Port field's value: the previous results go, a running scan is replaced.
|
||||
void start_scan(const FormPtr& fp) {
|
||||
Form& f = *fp;
|
||||
if (!f.alive) return;
|
||||
f.stop_scan();
|
||||
for (GtkWidget* r : f.results) adw_expander_row_remove(ADW_EXPANDER_ROW(f.scan_row), r);
|
||||
f.results.clear();
|
||||
adw_expander_row_set_expanded(ADW_EXPANDER_ROW(f.scan_row), FALSE);
|
||||
f.scan = std::make_unique<Scan>(static_cast<int>(value_of(f.port)));
|
||||
f.scan_ms = 0;
|
||||
set_scan_status(f, "Scanning...");
|
||||
f.timer = g_timeout_add_full(G_PRIORITY_DEFAULT, kTickMs, on_scan_tick, new FormPtr(fp),
|
||||
free_form_ptr);
|
||||
}
|
||||
|
||||
void on_scan_clicked(GtkButton*, gpointer user) {
|
||||
start_scan(*static_cast<FormPtr*>(user));
|
||||
}
|
||||
|
||||
class E1608Kind final : public SourceKind {
|
||||
public:
|
||||
std::string id() const override { return "e1608"; }
|
||||
@@ -253,8 +371,18 @@ class E1608Kind final : public SourceKind {
|
||||
f->ao_init[0] = spin_row("Initial DAC 0 (V)", -10, 10, 0.1, 0, 3);
|
||||
f->ao_init[1] = spin_row("Initial DAC 1 (V)", -10, 10, 0.1, 0, 3);
|
||||
|
||||
GtkWidget* conn = expander("Connection", {f->address, f->mac, f->subnet, f->port, f->code,
|
||||
f->timeout});
|
||||
f->scan_row = adw_expander_row_new();
|
||||
gtk_widget_set_name(f->scan_row, "e1608-scan-row");
|
||||
adw_preferences_row_set_title(ADW_PREFERENCES_ROW(f->scan_row), "Scan network");
|
||||
GtkWidget* scan_button = gtk_button_new_with_label("Scan");
|
||||
gtk_widget_set_name(scan_button, "e1608-scan");
|
||||
gtk_widget_set_valign(scan_button, GTK_ALIGN_CENTER);
|
||||
adw_expander_row_add_suffix(ADW_EXPANDER_ROW(f->scan_row), scan_button);
|
||||
g_signal_connect_data(scan_button, "clicked", G_CALLBACK(on_scan_clicked), new FormPtr(f),
|
||||
free_form, static_cast<GConnectFlags>(0));
|
||||
|
||||
GtkWidget* conn = expander("Connection", {f->scan_row, f->address, f->mac, f->subnet, f->port,
|
||||
f->code, f->timeout});
|
||||
GtkWidget* acq = expander("Acquisition", {f->rate, f->ext_clock, f->trigger, f->auto_restart,
|
||||
f->calibrate, f->poll_rate});
|
||||
GtkWidget* ai = expander("Analog inputs", {f->mode, f->ai[0], f->ai[1], f->ai[2], f->ai[3],
|
||||
@@ -283,7 +411,8 @@ class E1608Kind final : public SourceKind {
|
||||
ConfigForm out;
|
||||
out.rows = {conn, acq, ai, dio};
|
||||
out.description =
|
||||
"Give an address, or a MAC address to look up (optionally with a subnet such as 192.168.1)";
|
||||
"Give an address, or a MAC address to look up (optionally with a subnet such as 192.168.1), "
|
||||
"or scan the network for boards";
|
||||
out.focus = f->address;
|
||||
out.write = [f](const SourceConfig& c) { f->write(c); };
|
||||
out.read = [f]() { return f->read(); };
|
||||
|
||||
Reference in new issue
Block a user