GTK4: need button pressed and released signals

I am porting a large gtk3 application to gtk4.

I do need to control motions/hardware and start motion on button down and stop on release or out of focus for safety.

In gtk3 connecting “pressed” and “released” signals worked perfectly fine.
In gtk4 I only have the “clicked” signal. I tried the “activate” as substitute for “pressed” even not recommended, but had no luck or no response to it.

Below is some test code I attempted to recreate the events using gtk_gesture_click… and the pressed /released signals. I successfully used the gesture_click on a drawing_area widget, but it behaved strange on a button. All I get is the very first pres event, then nothing any more at all.
I have several buttons and have to create many similar constructs.

Is there any other way?

I do not understand why the more detailed signals are taken away from the application, also a focus signal can be useful.
I really do not want to create my own button, why?


mov_bp->grid_add_widget (button = gtk_button_new_from_icon_name ("seek-forward-symbolic"));
g_object_set_data( G_OBJECT (button), "DSP_cmd", GINT_TO_POINTER (DSP_CMD_AFM_MOV_ZM));
g_object_set_data( G_OBJECT (button), "MoverNo", GINT_TO_POINTER (i));
g_object_set_data( G_OBJECT (button), "AXIS-X", ec_axis[0]);
g_object_set_data( G_OBJECT (button), "AXIS-Y", ec_axis[1]);
g_object_set_data( G_OBJECT (button), "AXIS-Z", ec_axis[2]);
#if 0
g_signal_connect (G_OBJECT (button), "pressed",
                  G_CALLBACK (DSPMoverControl::CmdAction),
	        	    this);
g_signal_connect (G_OBJECT (button), "released",
  				    G_CALLBACK (DSPMoverControl::StopAction),
   				    this);
#endif
gesture = gtk_gesture_click_new ();
g_object_set_data( G_OBJECT (button), "DSP_cmd", GINT_TO_POINTER (DSP_CMD_Z0_P));
g_object_set_data( G_OBJECT (button), "MoverNo", GINT_TO_POINTER (99));
g_object_set_data( G_OBJECT (gesture), "Button", button);
//gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1);
g_signal_connect (gesture, "pressed", G_CALLBACK (DSPMoverControl::direction_button_pressed_cb), this);
g_signal_connect (gesture, "released", G_CALLBACK (DSPMoverControl::direction_button_released_cb), this);
gtk_widget_add_controller (button, GTK_EVENT_CONTROLLER (gesture));

Full code and project is “Gxsm4” (gxsm.sf.net), this file with issue is here:

https://sourceforge.net/p/gxsm/svn/HEAD/tree/trunk/Gxsm4/plug-ins/hard/sranger_mk2_hwi_mover.C

The signals you want haven’t been taken away altogether, there’s just a new way of accessing them. You need to attach GtkEventControllers to your button widget. There isn’t a specialised controller for mouse buttons; I think they’re handled by GtkEventControllerKey.

Hey Tony, yes that is what I hoped would do it and why I am asking. See the code!
(#If0#endif section = old gtk3 code)

I created and attached a gesture_click – but my problem is this does not work right the way I did it:

I am getting the very first push event, the nothing at all any more. And I do not understand it.
I thought there may be a conflict with the button event handling, but have no idea unless I broke some thing.

Those are my new handlers call my previous “Button pressed, …” callback:

        static void direction_button_pressed_cb (GtkGesture *gesture, int n_press, double x, double y, DSPMoverControl *dspc){
                g_message ("PRESSED");
                dspc->CmdAction (g_object_get_data( G_OBJECT (gesture), "Button"), dspc);
        };
        static void direction_button_released_cb (GtkGesture *gesture, int n_press, double x, double y, DSPMoverControl *dspc){
                g_message ("RELEASED");
                dspc->StopAction (g_object_get_data( G_OBJECT (gesture), "Button"), dspc);
        };

My first idea when reading your post some days ago was, that your button clicked signal conflicts with the event controller somehow. So do not use the clicked signal at all. Event controllers and gestures work, I am using them myself for a GTK4 drawing app and a GtkDrawingArea processing button press, release, motion and much more.

When you are still not able to get it working, just create a minimal example that we can compile and run, should be not a big effort.

Well, I do not connect the “clicked” event to the buttons here when the gesture is setup.

Yes I know it works like this, as I also use those same gestures with a drawing_area and there it does work fine!

I’ll create a mini program with a single button to test it or reproduce the issue as my application is quite large.

1 Like

OK, done. Good thing is:

A very minimal “hello world” app can reproduce my problem exactly!

Only the first push event. The silence.
What I am doing wrong here? Or did I found a bug?
Same behavior with or without the “clicked” event connected.

(I am using gtk4 as currently in Debian testing)

Play yourself:

meson.build:

project('hello', 'c',
          version: '4.3.0',
          meson_version: '>= 0.50.0',
)

executable('hello',
  [ 'hello-world.c' ],
  dependencies: [ dependency('gtk4') ],
  install: false
)

hello-world.c

#include <gtk/gtk.h>

static void
print_hello_clicked (GtkWidget *widget,
		     gpointer   data)
{
  g_message ("Hello World: Clicked\n");
}

static void
button_pressed (GtkGesture *gesture, int n_press, double x, double y, gpointer *user_data)
{
  g_message ("Hello World: Pressed\n");
}

static void
button_released (GtkGesture *gesture, int n_press, double x, double y, gpointer *user_data)
{
  g_message ("Hello World: Released\n");
}


static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *button;
  GtkGesture *gesture;
  
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Window");
  gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
  
  button = gtk_button_new_with_label ("Push Me");
  gtk_widget_set_halign (button, GTK_ALIGN_CENTER);
  gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
  // g_signal_connect (button, "clicked", G_CALLBACK (print_hello_clicked), NULL);

  gesture = gtk_gesture_click_new ();
  // gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1);
  g_signal_connect (gesture, "pressed", G_CALLBACK (button_pressed), NULL);
  g_signal_connect (gesture, "released", G_CALLBACK (button_released), NULL);
  gtk_widget_add_controller (button, GTK_EVENT_CONTROLLER (gesture));
  
  //g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);
  gtk_window_set_child (GTK_WINDOW (window), button);
  gtk_widget_show (window);
}

int
main (int    argc,
      char **argv)
{
  GtkApplication *app;
  int status;
  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}
1 Like

Nice work.

Indeed my second guess seems to help.

First guess was to put gtk_widget_add_controller() before the g_signal_connect() calls, but that make no difference. Second guess:

  //button = gtk_button_new_with_label ("Push Me");
    button = gtk_label_new("Push Me");

Obvious choice, why a button when a label will do. Maybe Mr Bassi call tell us why.

Note, we can compile your code just with

gcc -Wall t.c -o t pkg-config --cflags --libs gtk4

1 Like

OK… that seams to confirm a conflict with the button and it’s internal signals for highlighting it on focus and push!

I actually just replaced the button with a drawing area myself and found the same thing!

So what now? I really like and want the nice shaded buttons and visuals! :frowning:
Worked flawlessly in gtk3 and even earlier.

Sorry, can not help you further. So you may wait for Mr. Bassi or some of the other GTK experts, or maybe open an issue in GTK bugzilla issue tracker.

1 Like

Thanks a lot for your help! Good to know the problem is some where at the root and not in my code…
Unless a bug it should be documented if gestures can not be used on certain widgets!

is “Mr. Bassi” eventually reading this here?

Based on your example code.
I have a curiosity, what happens if you replace “released” with “stopped”, does trigger your button_released callback?

I connected stopped, yes it is called once. Then none of my requested event are emitted any more :frowning:

I did read up on it:
STOPPED:
Emitted whenever any time/distance threshold has been exceeded.

What does that mean here? Makes no sense to me.

@ebassi any insights here?


** Message: 12:42:09.147: Hello World: Clicked GTK-BUTTON
** Message: 12:42:09.497: Hello World: Gesture Stopped for GTK-BUTTON
** Message: 12:42:11.044: Hello World: Clicked GTK-BUTTON
** Message: 12:42:11.463: Hello World: Clicked GTK-BUTTON
** Message: 12:42:11.863: Hello World: Clicked GTK-BUTTON

Here is a little more advanced version for testing with GTK-BUTTON, LABEL and DRAWING-AREA (the last two work fine):

Yes stopped is happening :frowning:

And just for fun recreated a simple push button via drawing area. But can’t be a good solution or fix. I like to use the default button and get a simple pressed signal (most straightforward for me) or make use of the getsure if needed.

#include <gtk/gtk.h>

static void
wid_clicked (GtkWidget *widget,
	     gpointer   user_data)
{
  g_message ("Hello World:  Clicked %s", (const gchar*)user_data);
}

static void
wid_pressed (GtkGesture *gesture, int n_press, double x, double y, gpointer user_data)
{
  if (g_object_get_data( G_OBJECT (gesture), "area")){
    g_object_set_data( G_OBJECT (g_object_get_data( G_OBJECT (gesture), "area")), "state", GINT_TO_POINTER (1));
    gtk_widget_queue_draw (g_object_get_data( G_OBJECT (gesture), "area"));
  }
  g_message ("Hello World:  Pressed %s at (%g,%g)", (const gchar*)user_data, x,y);
}

static void
wid_released (GtkGesture *gesture, int n_press, double x, double y, gpointer user_data)
{
  if (g_object_get_data( G_OBJECT (gesture), "area")){
    g_object_set_data( G_OBJECT (g_object_get_data( G_OBJECT (gesture), "area")), "state", GINT_TO_POINTER (0));
    gtk_widget_queue_draw (g_object_get_data( G_OBJECT (gesture), "area"));
  }
  g_message ("Hello World: Released %s at (%g,%g)", (const gchar*)user_data, x,y);
}

static void
wid_stopped (GtkGesture *gesture, gpointer user_data)
{
  g_message ("Hello World: Gesture Stopped for %s", (const gchar*)user_data);
}


static void
draw_button (cairo_t *cr, int x, int y, int w, int h, int r)
{
    cairo_new_sub_path (cr);
    cairo_arc (cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
    cairo_arc (cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
    cairo_arc (cr, x + w - r, y + h - r, r, 0, M_PI / 2);
    cairo_arc (cr, x + r, y + h - r, r, M_PI / 2, M_PI);
    cairo_close_path (cr);
}

static void
draw_function (GtkDrawingArea *area, cairo_t *cr,
	       int             width,
	       int             height,
	       gpointer user_data)
{
  double b=5;
  double x0,y0;
  cairo_text_extents_t te;
  int state;
  
  gchar *l = user_data;
  if (!l)
    l = "DA: Push Me";

  x0=width/2;
  y0=height/2;
      
  state = GPOINTER_TO_INT (g_object_get_data( G_OBJECT (area), "state"));

  
  cairo_set_line_width (cr, 2);
  draw_button (cr, b, b, width-2*b, height-2*b, 5);
  cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);

  if (state == 0)
    cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
  else
    cairo_set_source_rgb (cr, 0.6, 0.6, 0.6);
  cairo_fill(cr);
  draw_button (cr, b, b, width-2*b, height-2*b, 5);
  if (state == 0)
    cairo_set_source_rgb (cr, 0.9, 0.7, 0.7);
  else
    cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
  cairo_stroke(cr);

  cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
  cairo_translate (cr, x0,y0);
  cairo_set_font_size (cr, 12);
  cairo_text_extents (cr, l, &te);
  cairo_move_to (cr, -te.width/2, te.height/2);
  cairo_show_text (cr, l);

}

static void
activate (GtkApplication *app,
          gpointer        user_data)
{
  GtkWidget *box;
  GtkWidget *window;
  GtkWidget *pushwid;
  GtkGesture *gesture;

  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "Gesture Tests");
  box = gtk_box_new(GTK_ORIENTATION_VERTICAL,10);
  
  // ====
  pushwid = gtk_button_new_with_label ("BTN: Push Me");
  gtk_box_append (GTK_BOX (box), pushwid);

  gesture = gtk_gesture_click_new ();
  // gtk_gesture_single_set_pushwid (GTK_GESTURE_SINGLE (gesture), 1);
  g_signal_connect (gesture, "pressed", G_CALLBACK (wid_pressed), "GTK-BUTTON");
  g_signal_connect (gesture, "released", G_CALLBACK (wid_released), "GTK-BUTTON");
  g_signal_connect (gesture, "stopped", G_CALLBACK (wid_stopped), "GTK-BUTTON");
  g_signal_connect (pushwid, "clicked", G_CALLBACK (wid_clicked), "GTK-BUTTON");
  gtk_widget_add_controller (pushwid, GTK_EVENT_CONTROLLER (gesture));

  // ====
  pushwid = gtk_label_new ("LAB: Push Me");
  gtk_box_append (GTK_BOX (box), pushwid);

  gesture = gtk_gesture_click_new ();
  // gtk_gesture_single_set_pushwid (GTK_GESTURE_SINGLE (gesture), 1);
  g_signal_connect (gesture, "pressed", G_CALLBACK (wid_pressed), "GTK-LABEL");
  g_signal_connect (gesture, "released", G_CALLBACK (wid_released), "GTK-LABEL");
  gtk_widget_add_controller (pushwid, GTK_EVENT_CONTROLLER (gesture));

  // ====
  pushwid = gtk_drawing_area_new ();
  gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (pushwid), 150);
  gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (pushwid), 50);
  gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (pushwid), draw_function, NULL, NULL);
  gtk_box_append (GTK_BOX (box), pushwid);

  gesture = gtk_gesture_click_new ();
  g_object_set_data( G_OBJECT (gesture), "area", pushwid);
  g_object_set_data( G_OBJECT (pushwid), "state", GINT_TO_POINTER (0));
  // gtk_gesture_single_set_pushwid (GTK_GESTURE_SINGLE (gesture), 1);
  g_signal_connect (gesture, "pressed", G_CALLBACK (wid_pressed), "GTK-DAREA");
  g_signal_connect (gesture, "released", G_CALLBACK (wid_released), "GTK-DAREA");
  gtk_widget_add_controller (pushwid, GTK_EVENT_CONTROLLER (gesture));
  
  //g_signal_connect_swapped (pushwid, "clicked", G_CALLBACK (gtk_window_destroy), window);
  gtk_window_set_child (GTK_WINDOW (window), box);
  gtk_widget_show (window);
}

int
main (int    argc,
      char **argv)
{
  GtkApplication *app;
  int status;
  app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}

@StefanSalewski
tried to make a copy of the original gtk button but got lost in too many gtk internal dependencies and gave up… I wish they would simply add and provide the pushed signal again.

Anyhow, something interesting I just figured out kind off by “accident” testing to hook up the gesture to a “icon” I added to my button via the get child as shown here – this is my solution, even a bit strange:

  pushwid = gtk_button_new_from_icon_name ("media-seek-forward-symbolic");
  gtk_box_append (GTK_BOX (box), pushwid);
  gesture = gtk_gesture_click_new ();
  g_signal_connect (gesture, "pressed", G_CALLBACK (wid_pressed), "GTK-BUTTON-SEEK");
  g_signal_connect (gesture, "released", G_CALLBACK (wid_released), "GTK-BUTTON-SEEK");
  g_signal_connect (gesture, "stopped", G_CALLBACK (wid_stopped), "GTK-BUTTON-SEEK");
  g_signal_connect (pushwid, "clicked", G_CALLBACK (wid_clicked), "GTK-BUTTON-SEEK");
  if (gtk_button_get_child (GTK_BUTTON (pushwid))){
    g_message ("HAS CHILD");
    gtk_widget_add_controller (gtk_button_get_child (GTK_BUTTON (pushwid)), GTK_EVENT_CONTROLLER (gesture));
  }else
    gtk_widget_add_controller (pushwid, GTK_EVENT_CONTROLLER (gesture));

and to my surprise I now get the pushed and also the stopped (after a second or so, but it keep functioning) this:

first I clicked the button with the gesture connected to the button widget,
then the other button as shown above with the icon and gesture on the icon child!

** Message: 20:18:14.045: HAS CHILD
** Message: 20:18:18.574: Hello World: Pressed GTK-BUTTON at (84.8414,9.27257)
** Message: 20:18:18.975: Hello World: Gesture Stopped for GTK-BUTTON
** Message: 20:18:19.441: Hello World: Clicked GTK-BUTTON
** Message: 20:18:23.009: Hello World: Clicked GTK-BUTTON
** Message: 20:18:23.760: Hello World: Clicked GTK-BUTTON (no good button for me)

and the button with icon:
** Message: 20:18:27.309: Hello World: Pressed GTK-BUTTON-SEEK at (58.9585,7.80492)
** Message: 20:18:27.424: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:27.424: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:28.120: Hello World: Pressed GTK-BUTTON-SEEK at (59.7758,9.86377)
** Message: 20:18:28.256: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:28.256: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:29.174: Hello World: Pressed GTK-BUTTON-SEEK at (60.5047,9.86377)
** Message: 20:18:29.575: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:30.425: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:32.390: Hello World: Pressed GTK-BUTTON-SEEK at (55.6618,14.3766)
** Message: 20:18:32.792: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:32.989: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:33.088: Hello World: Pressed GTK-BUTTON-SEEK at (54.662,14.2463)
** Message: 20:18:33.157: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:33.157: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:33.387: Hello World: Pressed GTK-BUTTON-SEEK at (55.4019,14.2463)
** Message: 20:18:33.445: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:33.445: Hello World: Clicked GTK-BUTTON-SEEK
** Message: 20:18:33.655: Hello World: Pressed GTK-BUTTON-SEEK at (55.0052,14.4591)
** Message: 20:18:33.708: Hello World: Gesture Stopped for GTK-BUTTON-SEEK
** Message: 20:18:33.709: Hello World: Clicked GTK-BUTTON-SEEK

… so that will indeed do for me – but certainly not what I expected!

I wonder and for curiosity will test how a “empty” button with no child will behave!

-P

@ebassi

I sincerely ask for some “under the hood” explanations here and help with the new gesture be(or miss be) haviors:

While I managed via the strange mixed use for the button signal “clicked” together with a gesture_click for “pressed” (also watching “stopped”) to get the simple event notifications for “button pressed” and “button released” = “clicked”.

There are two remaining issues:

a) the bad – not programming a game, but assume a “fire trigger button” functionality what surely needs to be released when the finger or mouse is off the trigger (button).

So now, I press the button and start fire (OK) then (may be accidentally) drift a bit off the button focus area and release the mouse button – and I get NOTHING, no signal at all!
That’s bad to dangerous in my case. You see my problem?

The stopped signal is useless for me as it always comes after a second or so and I most the time need to press or fire longer.

Previously in GTK3 this was very solid working and the release came alway, no matter where the mouse was no my screen, even on the very other end completely off the application. This behavior would be very appreciated. or a way to configure it.

b) After several (10…20+) short clicks or bursts on the same button all sudden I only keep getting “clicked” signals and no pushed and more. It recovers after a moment and works again. Oddly.

First of all, a bit of housekeeping: tagging people who are not in the thread is fairly rude. @StefanSalewski keeps doing it because I answer his questions, but he should stop trying to drag me into random topics. I’m not the only GTK developer, here, and I’m not at the beck and call of random people: I answer the questions that I want to answer, and that I can answer.

To come to your issue: you’re using a GtkButton and doing random press/release management, conflicting with the extant gesture that the button already uses. GtkButtons already have a click gesture, and they already handle the implicit grab that exists when you press the button and move the pointer while keeping it pressed.

If you want to handle your own press/release cycles, don’t use a GtkButton; you should use a custom GTK widget, instead.

I am very sorry, I did only tagged you as Stefan mentioned you above.

But many thanks, that helps me as in way that I know now not to look further into the button for this.
I realized for a drawing area can get the press/release behavior I need – only this requires me to re implement a button design, the hover highlight and a way to show a symbol.