I'm trying to understand this code example I found in the GTK+ 3.0 reference manual:
#include <gtk/gtk.h>
/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;
static void
clear_surface (void)
{
cairo_t *cr;
cr = cairo_create (surface);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_paint (cr);
cairo_destroy (cr);
}
/* Create a new surface of the appropriate size to store our scribbles */
static gboolean
configure_event_cb (GtkWidget *widget,
GdkEventConfigure *event,
gpointer data)
{
if (surface)
cairo_surface_destroy (surface);
surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
CAIRO_CONTENT_COLOR,
gtk_widget_get_allocated_width (widget),
gtk_widget_get_allocated_height (widget));
/* Initialize the surface to white */
clear_surface ();
/* We've handled the configure event, no need for further processing. */
return TRUE;
}
/* Redraw the screen from the surface. Note that the ::draw
* signal receives a ready-to-be-used cairo_t that is already
* clipped to only draw the exposed areas of the widget
*/
static gboolean
draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer data)
{
cairo_set_source_surface (cr, surface, 0, 0);
cairo_paint (cr);
return FALSE;
}
/* Draw a rectangle on the surface at the given position */
static void
draw_brush (GtkWidget *widget,
gdouble x,
gdouble y)
{
cairo_t *cr;
/* Paint to the surface, where we store our state */
cr = cairo_create (surface);
cairo_rectangle (cr, x - 3, y - 3, 6, 6);
cairo_fill (cr);
cairo_destroy (cr);
/* Now invalidate the affected region of the drawing area. */
gtk_widget_queue_draw_area (widget, x - 3, y - 3, 6, 6);
}
/* Handle button press events by either drawing a rectangle
* or clearing the surface, depending on which button was pressed.
* The ::button-press signal handler receives a GdkEventButton
* struct which contains this information.
*/
static gboolean
button_press_event_cb (GtkWidget *widget,
GdkEventButton *event,
gpointer data)
{
/* paranoia check, in case we haven't gotten a configure event */
if (surface == NULL)
return FALSE;
if (event->button == 1)
{
draw_brush (widget, event->x, event->y);
}
else if (event->button == 3)
{
clear_surface ();
gtk_widget_queue_draw (widget);
}
/* We've handled the event, stop processing */
return TRUE;
}
/* Handle motion events by continuing to draw if button 1 is
* still held down. The ::motion-notify signal handler receives
* a GdkEventMotion struct which contains this information.
*/
static gboolean
motion_notify_event_cb (GtkWidget *widget,
GdkEventMotion *event,
gpointer data)
{
int x, y;
GdkModifierType state;
/* paranoia check, in case we haven't gotten a configure event */
if (surface == NULL)
return FALSE;
/* This call is very important; it requests the next motion event.
* If you don't call gdk_window_get_pointer() you'll only get
* a single motion event. The reason is that we specified
* GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events().
* If we hadn't specified that, we could just use event->x, event->y
* as the pointer location. But we'd also get deluged in events.
* By requesting the next event as we handle the current one,
* we avoid getting a huge number of events faster than we
* can cope.
*/
gdk_window_get_pointer (event->window, &x, &y, &state);
if (state & GDK_BUTTON1_MASK)
draw_brush (widget, x, y);
/* We've handled it, stop processing */
return TRUE;
}
static void
close_window (void)
{
if (surface)
cairo_surface_destroy (surface);
gtk_main_quit ();
}
int
main (int argc,
char *argv[])
{
GtkWidget *window;
GtkWidget *frame;
GtkWidget *da;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 8);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (window), frame);
da = gtk_drawing_area_new ();
/* set a minimum size */
gtk_widget_set_size_request (da, 100, 100);
gtk_container_add (GTK_CONTAINER (frame), da);
/* Signals used to handle the backing surface */
g_signal_connect (da, "draw",
G_CALLBACK (draw_cb), NULL);
g_signal_connect (da,"configure-event",
G_CALLBACK (configure_event_cb), NULL);
/* Event signals */
g_signal_connect (da, "motion-notify-event",
G_CALLBACK (motion_notify_event_cb), NULL);
g_signal_connect (da, "button-press-event",
G_CALLBACK (button_press_event_cb), NULL);
/* Ask to receive events the drawing area doesn't normally
* subscribe to. In particular, we need to ask for the
* button press and motion notify events that want to handle.
*/
gtk_widget_set_events (da, gtk_widget_get_events (da)
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
Basically it's a simple scribble pad that demonstrates handling mouse events and drawing with Cairo. The manual says that normally a GDK_MOTION_NOTIFY event is received by a widget every time the mouse moves, but if you set the GDK_POINTER_MOTION_HINT_MASK flag you will only receive a single event until gdk_window_get_pointer
is called. After tinkering with the example that seems to just not be the case.
I modified the motion callback like so:
static gboolean
motion_notify_event_cb (GtkWidget *widget,
GdkEventMotion *event,
gpointer data)
{
int x, y;
GdkModifierType state;
printf("%2.2f, %2.2f is hint: ", event->x, event->y);
if(event->is_hint)
printf("yes\n");
else
printf("no\n");
fflush(stdout);
/* paranoia check, in case we haven't gotten a configure event */
if (surface == NULL)
return FALSE;
/* This call is very important; it requests the next motion event.
* If you don't call gdk_window_get_pointer() you'll only get
* a single motion event. The reason is that we specified
* GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events().
* If we hadn't specified that, we could just use event->x, event->y
* as the pointer location. But we'd also get deluged in events.
* By requesting the next event as we handle the current one,
* we avid getting a huge number of events faster than we
* can cope.
*/
//gdk_window_get_pointer (event->window, &x, &y, &state);
if(event->state & GDK_BUTTON1_MASK)
draw_brush (widget, event->x, event->y);
/* We've handled it, stop processing */
return TRUE;
}
Now when I run the program, the pointer coordinates are being printed on the terminal every time I move the mouse. This would imply that da
is still receiving motion events even though I have that flag set and I'm not asking for more events. Am I just not understanding how GDK_POINTER_MOTION_HINT_MASK works?
I should note that I am using GTK+ 3.24, and this manual I found is for GTK+ 3.0. I'm not sure this is even the official manual. When I compile the example I get a warning stating gdk_window_get_pointer
is deprecated and I should be using gdk_window_get_device_position
instead. I go to the API Reference at gtk.org and it's just straight up not complete. I can't find either of those functions nor can I find various other functions and structs mentioned elsewhere. Very frustrating!
So it just took me some time to figure out how to navigate the API Reference for the version I'm using, but i discovered that GDK_POINTER_MOTION_HINT_MASK had been deprecated since GTK version 3.8 where motion events were compressed by default (meaning only the latest motion event is received while the rest are discarded).