scope windows: a right-click menu of their own
A scope window is a view with its own arrangement, but its plot had no menu: every right-click went nowhere, because the main window's row menu acts on the main window's rows. `on_scope_right_click()` wires each scope window's GL area (beside its drop target) to a menu that acts on **that window's** row set: y axis auto scale, y axis fixed range (the main menu's dialog, aimed at the window's rows through `FixedRangeContext::rows`), fit, and one *Remove '<signal>'* per trace on the plot. A detached window's menu also carries **Attach back to the main window**. What it deliberately does not carry is an x fit: the window follows the main window's time span, so an x fit here would be undone by the next frame's mirror. `remove_trace()` and `FixedRangeContext` are generalized to take the row set they act on (the main window passes its own). Probe: `[scope-probe] rclick=1` asserts the click gesture is wired per window; the menu build is exercised by the real right-clicks the smoke makes.
This commit is contained in:
1 parent
b90a99ec39
commit
2924f7627d
5 files changed
+205
-10
No files matched your search
@@ -145,7 +145,9 @@ every frame, and pumps its render — so the two windows never disagree about th
|
|||||||
mode or the span. **A signal badge dragged onto a scope window's plot lands there** — the same
|
mode or the span. **A signal badge dragged onto a scope window's plot lands there** — the same
|
||||||
drop the main plot takes, into that window's own row set, on the cell the pointer is over — and
|
drop the main plot takes, into that window's own row set, on the cell the pointer is over — and
|
||||||
the signal page's *Plot on* buttons list **every window**: one group per window (named, `In
|
the signal page's *Plot on* buttons list **every window**: one group per window (named, `In
|
||||||
"plot 2"`), one button per plot of it.
|
"plot 2"`), one button per plot of it. A right-click on a scope window's plot opens **its own
|
||||||
|
menu** — y axis (auto or a fixed range), fit, remove a trace — acting on that window's rows; a
|
||||||
|
detached window's menu carries **Attach back to the main window** beside them.
|
||||||
mode or the span.
|
mode or the span.
|
||||||
|
|
||||||
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
|
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
|
||||||
|
|||||||
@@ -1473,6 +1473,7 @@ a defect in the copy. Measured output:
|
|||||||
[scope-probe] detach=1 scopes=3 frows=1 attach=1 scopes_back=2 frows_back=2 slot='ramp'
|
[scope-probe] detach=1 scopes=3 frows=1 attach=1 scopes_back=2 frows_back=2 slot='ramp'
|
||||||
[scope-probe] gl copy=ok(67f) empty=ok(39f) empty_bound=1
|
[scope-probe] gl copy=ok(67f) empty=ok(39f) empty_bound=1
|
||||||
[scope-probe] gl detached ok=1 frames=42 rows=1 slot0='sine'
|
[scope-probe] gl detached ok=1 frames=42 rows=1 slot0='sine'
|
||||||
|
[scope-probe] rclick=1
|
||||||
```
|
```
|
||||||
|
|
||||||
- `count=2` is both windows having been created and retained; `windows=1` each being a visible
|
- `count=2` is both windows having been created and retained; `windows=1` each being a visible
|
||||||
@@ -1492,6 +1493,9 @@ a defect in the copy. Measured output:
|
|||||||
signal into it" — landing in its row set. `drop=0` names a refused drop (a full plot or an
|
signal into it" — landing in its row set. `drop=0` names a refused drop (a full plot or an
|
||||||
unacquirable signal); `page_groups` less than the window count is a page rebuilt before the
|
unacquirable signal); `page_groups` less than the window count is a page rebuilt before the
|
||||||
windows were read or a group that lost its window.
|
windows were read or a group that lost its window.
|
||||||
|
- `rclick=1` is the scope window's **own right-click menu** being wired (one click gesture with
|
||||||
|
button 3 on its GL area, beside the drop target): the menu's actions act on that window's row
|
||||||
|
set.
|
||||||
- The fourth line is the windows' **GL account** (`gl copy=`/`gl empty=`): `ok(Nf)` is a context
|
- The fourth line is the windows' **GL account** (`gl copy=`/`gl empty=`): `ok(Nf)` is a context
|
||||||
that staged N frames, `err:` carries the adapter's failure message. A scope whose context
|
that staged N frames, `err:` carries the adapter's failure message. A scope whose context
|
||||||
failed draws *nothing* — no axes, no traces, exactly the "blank plot widget" report — so
|
failed draws *nothing* — no axes, no traces, exactly the "blank plot widget" report — so
|
||||||
|
|||||||
+173
-9
@@ -621,6 +621,12 @@ void open_scope_window(App& app, int figure_row, bool detached) {
|
|||||||
g_object_set_data(G_OBJECT(scope->drop), "udpscope-scope", scope.get());
|
g_object_set_data(G_OBJECT(scope->drop), "udpscope-scope", scope.get());
|
||||||
g_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app);
|
g_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app);
|
||||||
gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(scope->drop));
|
gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(scope->drop));
|
||||||
|
// The window's own right-click menu: its y scales, its traces, its removals -- the same
|
||||||
|
// actions the main window's plot menu carries, aimed at **this window's** row set.
|
||||||
|
GtkGesture* right = gtk_gesture_click_new();
|
||||||
|
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(right), 3);
|
||||||
|
g_signal_connect(right, "pressed", G_CALLBACK(on_scope_right_click), &app);
|
||||||
|
gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(right));
|
||||||
scope->detached = detached;
|
scope->detached = detached;
|
||||||
scope->window = GTK_WINDOW(gtk_window_new());
|
scope->window = GTK_WINDOW(gtk_window_new());
|
||||||
std::string title = figure_row >= 0
|
std::string title = figure_row >= 0
|
||||||
@@ -3338,8 +3344,9 @@ void on_y_auto_clicked(GtkButton* button, gpointer user) {
|
|||||||
|
|
||||||
void on_fixed_range_apply(GtkButton*, gpointer user) {
|
void on_fixed_range_apply(GtkButton*, gpointer user) {
|
||||||
FixedRangeContext* ctx = static_cast<FixedRangeContext*>(user);
|
FixedRangeContext* ctx = static_cast<FixedRangeContext*>(user);
|
||||||
ctx->app->rows->set_y_range(ctx->row, gtk_spin_button_get_value(GTK_SPIN_BUTTON(ctx->lo)),
|
RowSet& rows = ctx->rows != nullptr ? *ctx->rows : *ctx->app->rows;
|
||||||
gtk_spin_button_get_value(GTK_SPIN_BUTTON(ctx->hi)));
|
rows.set_y_range(ctx->row, gtk_spin_button_get_value(GTK_SPIN_BUTTON(ctx->lo)),
|
||||||
|
gtk_spin_button_get_value(GTK_SPIN_BUTTON(ctx->hi)));
|
||||||
gtk_window_destroy(GTK_WINDOW(ctx->window));
|
gtk_window_destroy(GTK_WINDOW(ctx->window));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3354,7 +3361,7 @@ void on_y_fixed_clicked(GtkButton* button, gpointer user) {
|
|||||||
gtk_box_append(GTK_BOX(box), labeled_box("min", lo));
|
gtk_box_append(GTK_BOX(box), labeled_box("min", lo));
|
||||||
gtk_box_append(GTK_BOX(box), labeled_box("max", hi));
|
gtk_box_append(GTK_BOX(box), labeled_box("max", hi));
|
||||||
|
|
||||||
auto* ctx = new FixedRangeContext{app, row, lo, hi, window};
|
auto* ctx = new FixedRangeContext{app, nullptr, row, lo, hi, window};
|
||||||
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
|
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
|
||||||
g_signal_connect_swapped(cancel, "clicked", G_CALLBACK(gtk_window_destroy), window);
|
g_signal_connect_swapped(cancel, "clicked", G_CALLBACK(gtk_window_destroy), window);
|
||||||
GtkWidget* apply = gtk_button_new_with_label("Apply");
|
GtkWidget* apply = gtk_button_new_with_label("Apply");
|
||||||
@@ -3374,9 +3381,9 @@ void on_y_fixed_clicked(GtkButton* button, gpointer user) {
|
|||||||
|
|
||||||
/// Frees one trace slot of a row. All it takes is the free: the row is rebuilt with a series that is
|
/// Frees one trace slot of a row. All it takes is the free: the row is rebuilt with a series that is
|
||||||
/// nothing, so it draws nothing and leaves the legend.
|
/// nothing, so it draws nothing and leaves the legend.
|
||||||
void remove_trace(App& app, int row, int slot) {
|
void remove_trace(RowSet& rows, int row, int slot) {
|
||||||
if (row < 0 || row >= app.rows->row_count()) return;
|
if (row < 0 || row >= rows.row_count()) return;
|
||||||
app.rows->assign(row, slot, std::string(), std::string(), 0);
|
rows.assign(row, slot, std::string(), std::string(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disables every math channel that (transitively) reads `name`, and answers with their names.
|
/// Disables every math channel that (transitively) reads `name`, and answers with their names.
|
||||||
@@ -3429,7 +3436,7 @@ int remove_signal_from_plots(App& app, const std::string& name) {
|
|||||||
// The slot remembers the *signal* (and its element), never the channel index -- the channel list is
|
// The slot remembers the *signal* (and its element), never the channel index -- the channel list is
|
||||||
// renumbered on every enable change, which is what this is reacting to.
|
// renumbered on every enable change, which is what this is reacting to.
|
||||||
if (app.rows->row(row).signal_name[static_cast<std::size_t>(slot)] != name) continue;
|
if (app.rows->row(row).signal_name[static_cast<std::size_t>(slot)] != name) continue;
|
||||||
remove_trace(app, row, slot);
|
remove_trace(*app.rows, row, slot);
|
||||||
++removed;
|
++removed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3448,14 +3455,14 @@ void on_remove_clicked(GtkButton* button, gpointer user) {
|
|||||||
int removed = 0;
|
int removed = 0;
|
||||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||||
if (r.channel[static_cast<std::size_t>(s)] < 0) continue;
|
if (r.channel[static_cast<std::size_t>(s)] < 0) continue;
|
||||||
remove_trace(app, target->row, s);
|
remove_trace(*target->app->rows, target->row, s);
|
||||||
++removed;
|
++removed;
|
||||||
}
|
}
|
||||||
what = "removed " + std::to_string(removed) + (removed == 1 ? " trace" : " traces") +
|
what = "removed " + std::to_string(removed) + (removed == 1 ? " trace" : " traces") +
|
||||||
" from plot " + app.rows->plot_label(target->row);
|
" from plot " + app.rows->plot_label(target->row);
|
||||||
} else {
|
} else {
|
||||||
const std::string name = r.name[static_cast<std::size_t>(target->slot)];
|
const std::string name = r.name[static_cast<std::size_t>(target->slot)];
|
||||||
remove_trace(app, target->row, target->slot);
|
remove_trace(*target->app->rows, target->row, target->slot);
|
||||||
what = "removed '" + name + "' from plot " + app.rows->plot_label(target->row);
|
what = "removed '" + name + "' from plot " + app.rows->plot_label(target->row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3656,6 +3663,163 @@ void on_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer
|
|||||||
open_row_menu(*app, row, x, y, widget);
|
open_row_menu(*app, row, x, y, widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scope-menu helpers: the rows carry their window, their plot and their slot, the popover to
|
||||||
|
/// dismiss, and the handlers are free functions -- a menu row's handler is a plain callback, not a
|
||||||
|
/// lambda, whose commas would split the `G_CALLBACK` macro into arguments.
|
||||||
|
App::ScopeWindow* scope_menu_scope(GtkButton* b) {
|
||||||
|
return static_cast<App::ScopeWindow*>(g_object_get_data(G_OBJECT(b), "udpscope-scope"));
|
||||||
|
}
|
||||||
|
int scope_menu_row(GtkButton* b) {
|
||||||
|
return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(b), "udpscope-row"));
|
||||||
|
}
|
||||||
|
void scope_menu_dismiss(GtkButton* b) {
|
||||||
|
GtkPopover* p = GTK_POPOVER(g_object_get_data(G_OBJECT(b), "udpscope-popover"));
|
||||||
|
if (p != nullptr) gtk_popover_popdown(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_scope_y_auto_clicked(GtkButton* b, gpointer) {
|
||||||
|
App::ScopeWindow* scope = scope_menu_scope(b);
|
||||||
|
if (scope == nullptr || scope->rows == nullptr) return;
|
||||||
|
scope->rows->set_y_auto(scope_menu_row(b), true);
|
||||||
|
scope_menu_dismiss(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main menu's fixed-range dialog, aimed at **this window's** rows: the dialog's Apply
|
||||||
|
/// writes the row set the context names, and the context carries the scope's.
|
||||||
|
void on_scope_y_fixed_clicked(GtkButton* b, gpointer user) {
|
||||||
|
App* a = static_cast<App*>(user);
|
||||||
|
App::ScopeWindow* scope = scope_menu_scope(b);
|
||||||
|
const int row = scope_menu_row(b);
|
||||||
|
if (scope == nullptr || scope->rows == nullptr || scope->rows->row(row).plot == nullptr) return;
|
||||||
|
GtkWidget* window = dialog_window(a->window, "Fixed Y range");
|
||||||
|
GtkWidget* box = gtk_window_get_child(GTK_WINDOW(window));
|
||||||
|
const plot::Range cur = scope->rows->row(row).plot->y_range(0);
|
||||||
|
GtkWidget* lo = spin(-1.0e12, 1.0e12, 0.1, 6, cur.min);
|
||||||
|
GtkWidget* hi = spin(-1.0e12, 1.0e12, 0.1, 6, cur.max);
|
||||||
|
gtk_box_append(GTK_BOX(box), labeled_box("min", lo));
|
||||||
|
gtk_box_append(GTK_BOX(box), labeled_box("max", hi));
|
||||||
|
auto* ctx = new FixedRangeContext{a, scope->rows.get(), row, lo, hi, window};
|
||||||
|
GtkWidget* cancel = gtk_button_new_with_label("Cancel");
|
||||||
|
g_signal_connect_swapped(cancel, "clicked", G_CALLBACK(gtk_window_destroy), window);
|
||||||
|
GtkWidget* apply = gtk_button_new_with_label("Apply");
|
||||||
|
g_object_set_data_full(G_OBJECT(apply), "udpscope-ctx", ctx,
|
||||||
|
[](gpointer p) { delete static_cast<FixedRangeContext*>(p); });
|
||||||
|
g_signal_connect(apply, "clicked", G_CALLBACK(on_fixed_range_apply), ctx);
|
||||||
|
dialog_add_button(window, cancel);
|
||||||
|
dialog_add_button(window, apply);
|
||||||
|
gtk_window_present(GTK_WINDOW(window));
|
||||||
|
scope_menu_dismiss(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The y fit of a scope window: the window's x follows the main window's span, so the fit is the
|
||||||
|
/// y axes alone -- `fit_all()`'s x reset would be undone by the next frame's mirror.
|
||||||
|
void on_scope_fit_clicked(GtkButton* b, gpointer) {
|
||||||
|
App::ScopeWindow* scope = scope_menu_scope(b);
|
||||||
|
if (scope == nullptr || scope->rows == nullptr) return;
|
||||||
|
scope->rows->set_y_auto(scope_menu_row(b), true);
|
||||||
|
scope_menu_dismiss(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_scope_remove_clicked(GtkButton* b, gpointer) {
|
||||||
|
App::ScopeWindow* scope = scope_menu_scope(b);
|
||||||
|
if (scope == nullptr || scope->rows == nullptr) return;
|
||||||
|
remove_trace(*scope->rows, scope_menu_row(b),
|
||||||
|
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(b), "udpscope-slot")));
|
||||||
|
scope_menu_dismiss(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_scope_attach_menu_clicked(GtkButton* b, gpointer user) {
|
||||||
|
App& a = *static_cast<App*>(user);
|
||||||
|
App::ScopeWindow* scope = scope_menu_scope(b);
|
||||||
|
if (scope == nullptr) return;
|
||||||
|
outlive_the_rebuild(GTK_WIDGET(b));
|
||||||
|
attach_scope_window(a, *scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scope window's own right-click menu. A scope window is a *view with its own arrangement*:
|
||||||
|
/// its y scales, its traces and its removals are its own, so the menu acts on **that window's**
|
||||||
|
/// row set -- the same rows the drop target and the signal page's *Plot on* groups write. What it
|
||||||
|
/// deliberately does not carry is an x fit: the window follows the main window's time span, so an
|
||||||
|
/// x fit here would be undone by the next frame.
|
||||||
|
void on_scope_right_click(GtkGestureClick* gesture, int, double x, double y, gpointer user) {
|
||||||
|
App* app = static_cast<App*>(user);
|
||||||
|
GtkWidget* widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
|
||||||
|
App::ScopeWindow* scope = nullptr;
|
||||||
|
for (const std::unique_ptr<App::ScopeWindow>& s : app->scopes) {
|
||||||
|
if (s->widget == widget) scope = s.get();
|
||||||
|
}
|
||||||
|
if (scope == nullptr || scope->rows == nullptr || scope->rows->row_count() <= 0) return;
|
||||||
|
const int row = std::clamp(static_cast<int>(scope->area->row_at(x, y)), 0,
|
||||||
|
scope->rows->row_count() - 1);
|
||||||
|
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
||||||
|
if (plot::input::Controller* c = scope->area->row_controller(static_cast<plot::u32>(row))) {
|
||||||
|
c->on_key(plot::input::KeyEvent{plot::input::kKeyEscape, 0, true});
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget* popover = gtk_popover_new();
|
||||||
|
gtk_widget_add_css_class(popover, "udpscope-menu");
|
||||||
|
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_widget_set_margin_top(box, 6);
|
||||||
|
gtk_widget_set_margin_bottom(box, 6);
|
||||||
|
gtk_widget_set_margin_start(box, 6);
|
||||||
|
gtk_widget_set_margin_end(box, 6);
|
||||||
|
const auto row_btn = [&](const char* label) {
|
||||||
|
GtkWidget* b = gtk_button_new_with_label(label);
|
||||||
|
gtk_widget_add_css_class(b, "flat");
|
||||||
|
gtk_widget_set_halign(b, GTK_ALIGN_FILL);
|
||||||
|
g_object_set_data(G_OBJECT(b), "udpscope-popover", popover);
|
||||||
|
g_object_set_data(G_OBJECT(b), "udpscope-scope", scope);
|
||||||
|
g_object_set_data(G_OBJECT(b), "udpscope-row", GINT_TO_POINTER(row));
|
||||||
|
gtk_box_append(GTK_BOX(box), b);
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
|
char markup[64];
|
||||||
|
std::snprintf(markup, sizeof(markup), "<b>Plot %s</b>", scope->rows->plot_label(row).c_str());
|
||||||
|
GtkWidget* heading = gtk_label_new(nullptr);
|
||||||
|
gtk_label_set_markup(GTK_LABEL(heading), markup);
|
||||||
|
gtk_widget_set_halign(heading, GTK_ALIGN_START);
|
||||||
|
gtk_box_append(GTK_BOX(box), heading);
|
||||||
|
|
||||||
|
GtkWidget* y_auto = row_btn("Y axis: auto scale");
|
||||||
|
g_signal_connect(y_auto, "clicked", G_CALLBACK(on_scope_y_auto_clicked), user);
|
||||||
|
GtkWidget* y_fixed = row_btn("Y axis: fixed range...");
|
||||||
|
g_signal_connect(y_fixed, "clicked", G_CALLBACK(on_scope_y_fixed_clicked), user);
|
||||||
|
GtkWidget* fit = row_btn("Fit Y axis");
|
||||||
|
g_signal_connect(fit, "clicked", G_CALLBACK(on_scope_fit_clicked), user);
|
||||||
|
|
||||||
|
// One removal per trace on the plot, flat rather than as a second view: a scope window holds
|
||||||
|
// at most its own figure's traces, and the menu is small.
|
||||||
|
bool traces = false;
|
||||||
|
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||||
|
const std::size_t si = static_cast<std::size_t>(s);
|
||||||
|
const std::string name = scope->rows->row(row).signal_name[si];
|
||||||
|
if (name.empty()) continue;
|
||||||
|
traces = true;
|
||||||
|
GtkWidget* rm = row_btn(("Remove '" + name + "'").c_str());
|
||||||
|
g_object_set_data(G_OBJECT(rm), "udpscope-slot", GINT_TO_POINTER(s));
|
||||||
|
g_signal_connect(rm, "clicked", G_CALLBACK(on_scope_remove_clicked), user);
|
||||||
|
}
|
||||||
|
if (!traces) {
|
||||||
|
GtkWidget* none = gtk_label_new("no traces on this plot");
|
||||||
|
gtk_widget_add_css_class(none, "dim-label");
|
||||||
|
gtk_widget_set_halign(none, GTK_ALIGN_START);
|
||||||
|
gtk_box_append(GTK_BOX(box), none);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope->detached) {
|
||||||
|
GtkWidget* back = row_btn("Attach back to the main window");
|
||||||
|
g_signal_connect(back, "clicked", G_CALLBACK(on_scope_attach_menu_clicked), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_popover_set_child(GTK_POPOVER(popover), box);
|
||||||
|
GdkRectangle rect{static_cast<int>(x), static_cast<int>(y), 1, 1};
|
||||||
|
gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rect);
|
||||||
|
gtk_widget_set_parent(popover, widget);
|
||||||
|
gtk_popover_popup(GTK_POPOVER(popover));
|
||||||
|
g_signal_connect(popover, "closed", G_CALLBACK(on_popover_closed), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
/// The availability of everything that does nothing until the stream has signals: the dock and the two
|
/// The availability of everything that does nothing until the stream has signals: the dock and the two
|
||||||
/// slide-over pages that were once in it (Cursors, Measurements). "Disable, do not hide": a page may be
|
/// slide-over pages that were once in it (Cursors, Measurements). "Disable, do not hide": a page may be
|
||||||
/// opened by its key before the first CONFIG, and shows its controls insensitive until then. The signal
|
/// opened by its key before the first CONFIG, and shows its controls insensitive until then. The signal
|
||||||
|
|||||||
@@ -1652,6 +1652,9 @@ struct ExportContext {
|
|||||||
};
|
};
|
||||||
struct FixedRangeContext {
|
struct FixedRangeContext {
|
||||||
App* app;
|
App* app;
|
||||||
|
/// The row set the range applies to: `nullptr` is the main window's. A scope window's own
|
||||||
|
/// dialog writes its window's rows.
|
||||||
|
RowSet* rows = nullptr;
|
||||||
int row;
|
int row;
|
||||||
GtkWidget* lo;
|
GtkWidget* lo;
|
||||||
GtkWidget* hi;
|
GtkWidget* hi;
|
||||||
@@ -2070,6 +2073,10 @@ gboolean on_scope_drop_channel(GtkDropTarget*, const GValue* value, double x, do
|
|||||||
/// drop target both end here). False, with a notice, when the signal cannot be acquired or the plot
|
/// drop target both end here). False, with a notice, when the signal cannot be acquired or the plot
|
||||||
/// holds four signals already.
|
/// holds four signals already.
|
||||||
bool assign_signal_to_scope(App& app, App::ScopeWindow& scope, std::size_t signal, int row);
|
bool assign_signal_to_scope(App& app, App::ScopeWindow& scope, std::size_t signal, int row);
|
||||||
|
/// The scope window's own right-click menu: the same row menu's actions, aimed at **that
|
||||||
|
/// window's** row set.
|
||||||
|
void on_scope_right_click(GtkGestureClick* gesture, int n_press, double x, double y,
|
||||||
|
gpointer user);
|
||||||
/// Reads each pane's own switch back off the header bar's button, and the other way round: the two are
|
/// Reads each pane's own switch back off the header bar's button, and the other way round: the two are
|
||||||
/// one control in two places, and `panel_switch_sync` keeps the write from looking like a click.
|
/// one control in two places, and `panel_switch_sync` keeps the write from looking like a click.
|
||||||
void sync_panel_switches(App& app);
|
void sync_panel_switches(App& app);
|
||||||
|
|||||||
@@ -1915,6 +1915,24 @@ gboolean scope_probe_step(gpointer user) {
|
|||||||
det.rows != nullptr && det.rows->row_count() > 0
|
det.rows != nullptr && det.rows->row_count() > 0
|
||||||
? det.rows->row(0).signal_name[0].c_str()
|
? det.rows->row(0).signal_name[0].c_str()
|
||||||
: "");
|
: "");
|
||||||
|
// The scope window carries its own right-click menu (its gesture is on the GL area, wired
|
||||||
|
// beside the drop target): one click gesture whose button is 3, per window.
|
||||||
|
int right_clicks = 0;
|
||||||
|
if (det.widget != nullptr) {
|
||||||
|
GListModel* controllers = gtk_widget_observe_controllers(det.widget);
|
||||||
|
const guint n = g_list_model_get_n_items(G_LIST_MODEL(controllers));
|
||||||
|
for (guint i = 0; i < n; ++i) {
|
||||||
|
GObject* c = G_OBJECT(g_list_model_get_item(controllers, i));
|
||||||
|
if (GTK_IS_GESTURE_SINGLE(c) &&
|
||||||
|
gtk_gesture_single_get_button(GTK_GESTURE_SINGLE(c)) == 3) {
|
||||||
|
++right_clicks;
|
||||||
|
}
|
||||||
|
g_object_unref(c);
|
||||||
|
}
|
||||||
|
g_object_unref(controllers);
|
||||||
|
}
|
||||||
|
std::fprintf(stderr, "[scope-probe] rclick=%d\n", right_clicks);
|
||||||
|
std::fflush(stderr);
|
||||||
}
|
}
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
|
|||||||
Reference in new issue
Block a user