Gtk.Scale "drag-end" signal not working

I’m trying to register to the “drag-end” signal on a Gtk.Scale to do some expensive updates, but the signal is emitted immediately after “drag-begin” for some reason, and I also don’t see any “drag-update” events. What am I doing wrong?

Here is a minimal example:

#include <gtk/gtk.h>

static void drag_begin(GtkEventControllerMotion* self, gpointer user_data)
{
    fprintf(stderr, "drag_begin()\n");
}
static void drag_update(GtkEventControllerMotion* self, gpointer user_data)
{
    fprintf(stderr, "drag_update()\n");
}
static void drag_end(GtkEventControllerMotion* self, gpointer user_data)
{
    fprintf(stderr, "drag_end()\n");
}

static void activate(GtkApplication* app, gpointer user_data)
{
    GtkWidget*     window;
    GtkWidget*     grid;
    GtkWidget*     scale;
    GtkAdjustment* adj;
    GtkGesture*    gesture;

    adj = gtk_adjustment_new(5.0, 1.0, 10.0, 0.0, 0.0, 0.0);
    scale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, adj);
    gtk_widget_set_hexpand(scale, TRUE);

    gesture = gtk_gesture_drag_new();
    g_signal_connect(gesture, "drag-begin", G_CALLBACK(drag_begin), NULL);
    g_signal_connect(gesture, "drag-update", G_CALLBACK(drag_update), NULL);
    g_signal_connect(gesture, "drag-end", G_CALLBACK(drag_end), NULL);
    gtk_widget_add_controller(scale, GTK_EVENT_CONTROLLER(gesture));

    grid = gtk_grid_new();
    gtk_grid_attach(GTK_GRID(grid), scale, 0, 0, 1, 1);

    window = gtk_application_window_new(app);
    gtk_window_set_child(GTK_WINDOW(window), grid);
    gtk_widget_set_visible(window, 1);
}

int main(int argc, char** argv)
{
    GtkApplication* app;
    int             status = EXIT_FAILURE;

    app = gtk_application_new("com.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

Gtk.Scale doesn’t seem to have a drag-end signal. And your code appears to be inserting an event controller into an existing widget (as opposed to a widget adding an event controller to itself)—this is something you generally shouldn’t do. Then, gtk_gesture_drag_new() creates a Gtk.GestureDrag, not a Gtk.EventControllerMotion which your handlers are expecting.

To avoid the XY problem, can you describe what you’re actually trying to achieve, and what led you to this attempted solution?

Sure. What I need is some kind of “infinite logarithmic slider” widget. I’m developing a tool for designing filters in the field of electronics. The equations have physical parameters, and I’d like to be able to “tweak” them using sliders:

The problem is that there is no lower or upper bound to these values, and a linear scale is not suitable either. For example, k is currently 4.5. If I move the slider all the way to the left then I’d like the value to go to 0.45 (one magnitude smaller). But if I move it all the way to the right, then I’d like the value to be 45 (one magnitude larger). Once I let go, the slider should snap to the middle again and the bounds should update to whatever the new range is. If the value had been 450 instead, then the lower bound would be 45 and the upper 4500.

I’m currently trying to do this using Gtk.Scale but I’m open to other ideas. Maybe some kind of knob that can rotate infinitely makes more sense? The important thing is that the scale is logarithmic.

So to get back to the original question: I need drag-end in order to change the Gtk.Adjustment ranges when the slider is let go. And I need drag-update to allow scrolling beyond the normal bounds of a Gtk.Scale. The idea of attaching my own Gtk.GestureDrag came from reading the source code of gtkrange.c:575 and I was under the impression you could add multiple gestures to the same object.

But Gtk.Scale is designed to control a Gtk.Adjustment, so it works linearly.

That would likely take the user by surprise, since sliders/scales don’t typically behave like that at all.

What about all the other ways to interact with the scale:

  • keyboard (it has a bunch of key bindings doing various things)
  • scrolling while hovering on top of it
  • clicking/tapping on the scale (with various mouse buttons, possibly with Shift held) — see gtk_range_click_gesture_pressed
  • controlling the scale through accessibility interfaces (it implements Gtk.AccessibleRange)

While typically my advice would be to use the existing GTK widgets as much as possible since they comes with a lot of functionality, it sounds like your case is different enough from how Gtk.Scale is supposed to be used that you should write your own widget. (Alternatively, if this is just some proof-of-concept prototype, then maybe you could indeed do some quick hacky thing and move on to other functionality of your app.)

Yeah, so it might make sense to use a different metaphor than a scale/ruler entirely. Alternatively, maybe a “logarithmic scale” (i.e. a slide rule) metaphor could work, but that would be a different widget, not Gtk.Scale.

You can indeed. But the way the gesture framework works, once a gesture decides to “claim” the current event sequence, all the other gestures stop handling it. This must be why you’re seeing your drag immediately end when the actual Gtk.Scale’s drag gesture claims the drag sequence. See more about states here.

If this is a quick proof-of-concept, you could find the existing drag gesture on the Gtk.Scale, and connect your handler to its drag-end signal. But in a more serious app, please don’t do that.