GTK3 mouse response problems

I’m using GTK3 for only the second time so forgive me if I missed something obvious. I have a program which creates a GtkWindow, some GtkBox objects and GtkDrawingAreas. The only thing set to receive events is the Window, though I haven’t explicitly disabled them for anything else. There are two problems.

Firstly, when I drag the mouse I get a button-press event, then two motion-notify-events and then nothing until I let go of the button. There after I get motion-notify again. I’ve checked various aspects but I can’t understand why it stops after two movements.

Second problem, the mouse wheel event reports direction as GDK_SCROLL_SMOOTH with zero for delta_x and delta_y no matter what I do with the wheel. I tried removing GDK_SMOOTH_SCROLL_MASK from gtk_widget_add_events in the hope of getting GDK_SCROLL_UP and GDK_SCROLL_DOWN but it still sends smooth scroll messages. Is it possible I’m calling gtk_widget_add_events at the wrong time?

I’m using Kubuntu. Again, my apologies if the problem is obvious.

I have no real idea what you intent – you have multiple drawing areas, so you intent using drag and drop for the drawing areas and other widgets? Because you do not write “move the mouse” but instead “drag the mouse”.

You dont tell us which programming language you are using, but I start generally when possible with code similar to the few available C examples. For drawing area there is an example included in gtk3-demo, called scribble window. It is also available as a single file at Gtk – 3.0. That code is not too smart, but it helps you to get the events right. I started with it when I created the initial Ruby version of GitHub - StefanSalewski/gintro: High level GObject-Introspection based GTK3/GTK4 bindings for Nim language ten years ago. For drag-and-drop there should exists also some C examples, maybe also in get3-demo and related code, but I have no idea how drag-and-drop should be used with drawing area.

Hi Stefan, thanks for responding. I am using good old C in this case.

I actually progressed with the second problem since yesterday. It seems that GTK sends a GDK_SCROLL_SMOOTH with a zero delta before it sends GDK_SCROLL_UP or GDK_SCROLL_DOWN and whether the mouse supports smooth scrolling or not. So I made the code ignore the zero delta and its working fine. I guess that it would have accurate smooth deltas if I connected a trackpad and used a two finger scrolling gesture rather than a mouse wheel but that’s for later testing.

I looked carefully at both the examples and I’m doing it right as far as I can tell. The only real difference I can see is that its not the drawing area which sets the event handler but the window which is their parent. The event process itself seems very logical, one of the things I like about GTK. I’m just not sure why its only getting two motion events.

To be clear, the intention is to handle drag operations on things shown in the drawing area. So this is not a drag-and-drop operation. The drag is moving an object shown by the drawing area. However, I’m trying to handle all the mouse movement in the main window.

Is it possible that the drawing area is grabbing the mouse somehow? Perhaps GTK thinks I’m trying to drag the drawing area itself? I assumed that I had made the drawing area invisible to the mouse by not giving it a motion-notify handler? Is that right?

That is interesting. I had to move “objects” myself in the drawingarea in my old Ruby schematics editor Homepage of Dr. Stefan Salewski, but I interpreted just plain button up/down and mouse move events myself. Would be interested if GTK’s drag operations can be used for that, as I intend to make a new Nim version eventually of that and related tools.

I have never got a GDK_SCROLL_SMOOTH event myself until now, still have to learn what it really is…

To move this along, I removed the drawing area entirely. Theres now just one GtkWindow and I’m dragging on that.

When I say, I “drag” I mean tracking motion-events while one of the mouse buttons is held. I get button-press then two motion events, then no more until I let go of the button. Then I get a release event and stream of motion events again.

Do I have to handle motion-event while holding the button down in a specific way?

Maybe posting a minimal example would show the problem. You might also consider using GtkGestureDrag instead.

Well, that is what I did in my old Ruby app Homepage of Dr. Stefan Salewski, that works fine but the logic for many different actions is not always that easy. But I like smart behaviour better that the dump modal behaviour which old tools like gschem provided, where you very often have to change the mode of operation like draw net, rotate net segment, move endpoint, connect net and all that.

I have implemented that without enabling special gtk drag and drop support, only plain motion events with qualifiers. You can test behaviour easily – just modify the scribble example.

One thing worth giving a try is to see if changing the return value of the button press callback to TRUE makes a difference.

It might be better to use an event box in the GtkBox to capture the motion. It might make figuring out the event propagation easier and you probably don’t want to block motion in the GtkWindow…

Eric

static gboolean button_press_event_window(GtkWidget *window, GdkEventButton *event, gpointer user_data)
  {
    g_signal_handler_unblock(window, window_motion_id);
    return TRUE;
  }
static gboolean button_release_event_window(GtkWidget *window, GdkEventButton *event, gpointer user_data)
  {
    g_signal_handler_block(window, window_motion_id);
    return TRUE;
  }

This is a very sensible idea. First reveal though, I’m using Objective-C because the app is cross platform and intended to work on a Mac. I know that’s not a popular language but apart from the function declarations its just C so won’t make any real difference. Any weird square brackets in the code are just function calls.

This is the code to setup the window and its event handlers.

wnd = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_add_events (GTK_WIDGET (wnd), GDK_ALL_EVENTS_MASK);
gtk_drag_source_unset (wnd);
g_signal_connect (wnd, "realize", G_CALLBACK (realize), self);
g_signal_connect (wnd, "key-release-event", G_CALLBACK (keyUp), self);
g_signal_connect (wnd, "button-press-event", G_CALLBACK (buttonPress), self); 
g_signal_connect (wnd, "button-release-event", G_CALLBACK (buttonRelease), self); 
g_signal_connect (wnd, "motion-notify-event", G_CALLBACK (mouseMove), self); 
g_signal_connect (wnd, "scroll-event", G_CALLBACK (mouseWheel), self);
g_signal_connect (wnd, "size-allocate", G_CALLBACK (resize), self);
g_signal_connect (wnd, "delete-event", G_CALLBACK (closeWindow), self);

These are the event handlers. I haven’t included the ones that don’t relate to mouse activity, size , delete etc

void realize (GtkWidget *widget, nxWindow *wnd)
{
	wnd->wgt = gtk_widget_get_window (widget);
}

gboolean buttonPress (GtkWidget *widget, GdkEventButton *event, nxWindow *wnd)
{
	if (gdk_event_triggers_context_menu ((GdkEvent *) event))
	{
		[wnd contextMenu: event];
	}
	else
	{
		if (!modState)
		{
			nxPoint pos = {event->x, event->y};
			modState = event->button;
		
			if (![wnd pressMouse: &pos: modState])
			{
				modState = 0;
			}

			return TRUE;
		}
	}

	return FALSE;
}

gboolean buttonRelease (GtkWidget *widget, GdkEventButton *event, nxWindow *wnd)
{
	nxPoint pos = {event->x, event->y};

	if (event->button == (modState & nxMOUSE_MASK))
	{
		[wnd liftMouse: &pos: modState];
		modState = 0;
		return TRUE;
	}

	return FALSE;
}

gboolean mouseMove (GtkWidget *widget, GdkEventMotion *event, nxWindow *wnd)
{
	nxPoint pos = {event->x, event->y};

	if (modState)
	{
		[wnd dragMouse: &pos: modState];
	}
	else
	{
		[wnd moveMouse: &pos: modState];
	}

	return TRUE;
}

I’m almost certain the callback are returning TRUE all the while. I will follow you suggestion and try a GtkEventBox next. See if that has any significant effect.

I will try gestures but its somewhat confusing. All the documentation says that pointer-motion events should be enough and that is consistent with other platforms. Gestures are usually a touchscreen thing and using them for mouse motion seems contradictory.

Still, I will give that a go if the GtkEventBox suggestion doesn’t help though I’d really prefer to understand why pointer-motion doesn’t work correctly.

There’s a lot to unpack, here…

There are multiple of things that may cause this.

  • you may be blocking the event loop
  • you may be triggering an input grab in your code, especially if you’re doing things like dragging and dropping something
  • you may be hitting the motion event compression (multiple motion events are automatically discarded if they happen within the same frame)
  • drawing areas do not have an input window by default, so you’re going to receive events intercepted by the first windowing system surface (GdkWindow, in GTK terms) created by one of the ancestors of the drawing area widget

GTK uses GtkGesture as the API for event handling with some state—not just for touch input devices, but for everything else.

Event handling based on widget signals is being phased out, and has been removed from GTK4 (the next stable version of GTK currently in development); gestures are the preferred API for event handling.

I don’t know why you are only getting two motion events. The square brackets trip me up also. Tested cursor motion over a GtkWindow and a GtkEventBox. A little different but they both work good.

Eric


//gcc -Wall motion1.c -o motion1 `pkg-config --cflags --libs gtk+-3.0`

//Tested on Ubuntu18.04 and GTK3.22

#include<gtk/gtk.h>

//For blocking motion signal.
static guint window_motion_id=0;
static guint event_box_motion_id=0;
//The motion x and y coordinates.
static gdouble event_box_x=0.0;
static gdouble event_box_y=0.0;
static gdouble window_x=0.0;
static gdouble window_y=0.0;

static gboolean draw_window(GtkWidget *window, cairo_t *cr, gpointer user_data);
static gboolean draw_event_box(GtkWidget *event_box, cairo_t *cr, gpointer user_data);
static gboolean draw_da(GtkWidget *da, cairo_t *cr, gpointer user_data);
static gboolean button_press_window(GtkWidget *window, GdkEventButton *event, GtkWidget *da);
static gboolean button_release_window(GtkWidget *window, GdkEventButton *event, GtkWidget *da);
static gboolean motion_notify_window(GtkWidget *window, GdkEventMotion *event, GtkWidget *da);
static gboolean button_press_event_box(GtkWidget *da1, GdkEventButton *event, GtkWidget *da);
static gboolean button_release_event_box(GtkWidget *da1, GdkEventButton *event, GtkWidget *da);
static gboolean motion_notify_event_box(GtkWidget *da1, GdkEventMotion *event, GtkWidget *da);

int main(int argc, char **argv)
  {
    gtk_init(&argc, &argv);

    GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "Motion");
    gtk_window_set_default_size(GTK_WINDOW(window), 600, 200);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    gtk_widget_set_app_paintable(window, TRUE);
    g_signal_connect(window, "draw", G_CALLBACK(draw_window), NULL);

    GtkWidget *event_box=gtk_event_box_new();
    gtk_widget_set_size_request(event_box, 200, 200);
    gtk_widget_set_app_paintable(event_box, TRUE);
    g_signal_connect(event_box, "draw", G_CALLBACK(draw_event_box), NULL);

    GtkWidget *da=gtk_drawing_area_new();
    gtk_widget_set_size_request(da, 200, 200);
    g_signal_connect(da, "draw", G_CALLBACK(draw_da), NULL);

    window_motion_id=g_signal_connect(window, "motion-notify-event", G_CALLBACK(motion_notify_window), da);
    //g_signal_handler_block(window, window_motion_id);
    g_signal_connect(window, "button-press-event", G_CALLBACK(button_press_window), da);
    g_signal_connect(window, "button-release-event", G_CALLBACK(button_release_window), da);
 
    event_box_motion_id=g_signal_connect(event_box, "motion-notify-event", G_CALLBACK(motion_notify_event_box), da);
    //g_signal_handler_block(event_box, event_box_motion_id);
    g_signal_connect(event_box, "button-press-event", G_CALLBACK(button_press_event_box), da);
    g_signal_connect(event_box, "button-release-event", G_CALLBACK(button_release_event_box), da);

    GtkWidget *box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(box), event_box, FALSE, FALSE, 0);
    gtk_box_pack_end(GTK_BOX(box), da, FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(window), box);

    gtk_widget_show_all(window);

    gtk_main();

    return 0;
  }
static gboolean draw_window(GtkWidget *window, cairo_t *cr, gpointer user_data)
  {
    cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 1.0);
    cairo_paint(cr);

    cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
    cairo_set_font_size(cr, 16);
    cairo_move_to(cr, 330.0, 20.0);
    cairo_show_text(cr, "window");

    return FALSE;
  }
static gboolean draw_event_box(GtkWidget *event_box, cairo_t *cr, gpointer user_data)
  {
    cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
    cairo_paint(cr);

    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_set_font_size(cr, 16);
    cairo_move_to(cr, 110.0, 20.0);
    cairo_show_text(cr, "event box");

    return FALSE;
  }
static gboolean draw_da(GtkWidget *da, cairo_t *cr, gpointer user_data)
  {
    cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
    cairo_paint(cr);

    cairo_set_source_rgba(cr, 1.0, 1.0, 0.0, 1.0);
    cairo_arc(cr, window_x-200.0, window_y, 20.0, 0.0, 2.0*G_PI);
    cairo_fill(cr);

    cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
    cairo_arc(cr, event_box_x-400.0, event_box_y, 20.0, 0.0, 2.0*G_PI);
    cairo_fill(cr);

    cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
    cairo_set_font_size(cr, 16);
    cairo_move_to(cr, 150.0, 20.0);
    cairo_show_text(cr, "da");

    return FALSE;
  }
static gboolean button_press_window(GtkWidget *window, GdkEventButton *event, GtkWidget *da)
  {
    g_print("button press window\n");
    //g_signal_handler_unblock(window, window_motion_id);
    window_x=-200.0+event->x;
    window_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }
static gboolean button_release_window(GtkWidget *window, GdkEventButton *event, GtkWidget *da)
  {
    g_print("button release window\n");
    //g_signal_handler_block(window, window_motion_id);
    window_x=-200.0+event->x;
    window_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }
static gboolean motion_notify_window(GtkWidget *window, GdkEventMotion *event, GtkWidget *da)
  {
    window_x=-200.0+event->x;
    window_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }
static gboolean button_press_event_box(GtkWidget *event_box, GdkEventButton *event, GtkWidget *da)
  {
    g_print("    button press event box\n");
    //g_signal_handler_unblock(event_box, event_box_motion_id);
    event_box_x=event->x;
    event_box_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }
static gboolean button_release_event_box(GtkWidget *event_box, GdkEventButton *event, GtkWidget *da)
  {
    g_print("    button release event_box\n");
    //g_signal_handler_block(event_box, event_box_motion_id);
    event_box_x=event->x;
    event_box_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }
static gboolean motion_notify_event_box(GtkWidget *event_box, GdkEventMotion *event, GtkWidget *da)
  {
    event_box_x=event->x;
    event_box_y=event->y;
    gtk_widget_queue_draw(da);

    return TRUE;
  }

Thanks for responding. This gives me some interesting things to look at.

What would block the event loop?

There are no explicit grabs in the code at all. I haven’t tried that part of GTK yet. I understand that GTK does implicit grabs with the mouse, but I don’t know how they could cause this behavior or how I would prevent that if they did.

Compression sounds like it has potential. The code which does the interaction caused by the drag operation finally calls gtk_widget_queue_draw_area(). I understand that this causes a frame tick? A “paint” phase is triggered and that is apparent from the display when the window contents do change once.

I had assumed that the pointer-motion messages causing additions to the draw queue will drive the frame clock. If there are several pointer-events before each draw phase, that’s no problem. Their effect will be summed up but the display is only changed from the “draw” event. Do I need to do something else to cause the frame clock to continue?

As an experiment, I tried removing the draw queuing but it made no difference that I could see. The program responds to the mouse wheel by redrawing with the same process and that works reliably.

I have removed the DrawingArea entirely now but it doesn’t seem to have made any difference. I was always intending to receive events on the GtkWindow and thats all I have now, just the one window.

I seemed to have figured it out although I’m not entirely sure why it works. If the motion-notify callback returns TRUE, it gets only one event. If it returns FALSE then motion events continue as expected.

Not sure why that makes the difference in this case. The documentation only says that TRUE stops other handlers being invoked. I don’t have any other handlers in this case so I guess its something in GTK?

Thanks for the many responses. They did prompt me to try several new ways to do things and that has been useful. I may still try the gesture idea even though it structures the code quite differently to the other platforms it runs on.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.