diff --git a/ChangeLog b/ChangeLog index 5c55c4f98..be5d8d0ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2007-11-15 Øyvind Kolås + + * clutter/clutter-main.[ch]: added clutter_grab_pointer, + clutter_ungrab_pointer and clutter_get_pointer_grab, in + clutter_do_event deliver pointer related events only to the + actor with the pointer grab if a grab exists. + * clutter/clutter-private.h: added pointer_grab_actor to context. + * tests/Makefile.am: + * tests/test-grab.c: added test for testing the pointer grab. + 2007-11-15 Emmanuele Bassi * clutter/clutter-timeline.[ch]: Add ClutterTimeline:direction diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index 0f0e75dfe..1b482505f 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -1240,9 +1240,14 @@ clutter_do_event (ClutterEvent *event) break; case CLUTTER_ENTER: case CLUTTER_LEAVE: - { - deliver_event (event); - } + + if (context->pointer_grab_actor != NULL) + { + clutter_actor_event (context->pointer_grab_actor, event, FALSE); + return; + } + + deliver_event (event); break; case CLUTTER_DESTROY_NOTIFY: case CLUTTER_DELETE: @@ -1269,6 +1274,12 @@ clutter_do_event (ClutterEvent *event) /* Only stage gets motion events */ event->any.source = stage; + if (context->pointer_grab_actor != NULL) + { + clutter_actor_event (context->pointer_grab_actor, event, FALSE); + return; + } + /* Trigger handlers on stage in both capture .. */ if (!clutter_actor_event (stage, event, TRUE)) { @@ -1288,14 +1299,24 @@ clutter_do_event (ClutterEvent *event) clutter_event_get_coords (event, &x, &y); /* Handle release off stage */ - if (x >= CLUTTER_STAGE_WIDTH () || - y >= CLUTTER_STAGE_HEIGHT() || - x < 0 || y < 0) + if ((x >= CLUTTER_STAGE_WIDTH () || + y >= CLUTTER_STAGE_HEIGHT() || + x < 0 || y < 0)) { + if (event->type == CLUTTER_BUTTON_RELEASE) { CLUTTER_NOTE (EVENT, "Release off stage received at %i, %i", x, y); + event->button.source = stage; + + if (context->pointer_grab_actor != NULL) + { + clutter_actor_event (context->pointer_grab_actor, + event, FALSE); + return; + } + deliver_event (event); } break; @@ -1305,6 +1326,9 @@ clutter_do_event (ClutterEvent *event) actor = _clutter_do_pick (CLUTTER_STAGE (stage), x, y, CLUTTER_PICK_REACTIVE); + + event->any.source = actor; + if (!actor) break; @@ -1316,7 +1340,6 @@ clutter_do_event (ClutterEvent *event) CLUTTER_NOTE (EVENT, "Reactive event received at %i, %i - actor: %p", x, y, actor); - event->any.source = actor; /* Motion enter leave events */ if (event->type == CLUTTER_MOTION) @@ -1355,7 +1378,13 @@ clutter_do_event (ClutterEvent *event) } else event_click_count_generate (event); - + + if (context->pointer_grab_actor != NULL) + { + clutter_actor_event (context->pointer_grab_actor, event, FALSE); + return; + } + deliver_event (event); } break; @@ -1429,3 +1458,88 @@ clutter_set_default_frame_rate (guint frames_per_sec) if (context->frame_rate != frames_per_sec) context->frame_rate = frames_per_sec; } + + +static void +on_pointer_grab_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + ClutterMainContext *context; + + context = clutter_context_get_default (); + context->pointer_grab_actor = NULL; + + clutter_ungrab_pointer (); +} + +/** + * clutter_grab_pointer: + * @actor: a #ClutterActor + * + * Grabs pointer events, after the grab is done all pointer related events + * (press, motion, release, enter, leave and scroll) are delivered to this + * actor directly. The source set in the event will be the actor that would + * have received the event if the pointer grab was not in effect. + * + * Since: 0.6 + */ +void +clutter_grab_pointer (ClutterActor *actor) +{ + ClutterMainContext *context; + + g_return_if_fail (actor == NULL || CLUTTER_IS_ACTOR (actor)); + + context = clutter_context_get_default (); + + if (context->pointer_grab_actor == actor) + return; + + if (context->pointer_grab_actor) + { + g_object_weak_unref (G_OBJECT (context->pointer_grab_actor), + on_pointer_grab_weak_notify, + NULL); + context->pointer_grab_actor = NULL; + } + + if (actor) + { + context->pointer_grab_actor = actor; + + g_object_weak_ref (G_OBJECT (actor), + on_pointer_grab_weak_notify, + NULL); + } +} + +/** + * clutter_ungrab_pointer: + * + * Removes an existing grab of the pointer. + * + * Since: 0.6 + */ +void +clutter_ungrab_pointer (void) +{ + clutter_grab_pointer (NULL); +} + +/** + * clutter_get_pointer_grab: + * + * Queries the current pointer grab of clutter. + * + * Return value: the actor currently holding the pointer grab, or NULL if there is no grab. + * + * Since: 0.6 + */ +ClutterActor * +clutter_get_pointer_grab (void) +{ + ClutterMainContext *context; + context = clutter_context_get_default (); + + return context->pointer_grab_actor; +} diff --git a/clutter/clutter-main.h b/clutter/clutter-main.h index 0ebe6490a..abb70d9bb 100644 --- a/clutter/clutter-main.h +++ b/clutter/clutter-main.h @@ -106,6 +106,11 @@ gboolean clutter_get_motion_events_enabled (void); guint clutter_get_default_frame_rate (void); void clutter_set_default_frame_rate (guint frames_per_sec); + +void clutter_grab_pointer (ClutterActor *actor); +void clutter_ungrab_pointer (void); +ClutterActor * clutter_get_pointer_grab (void); + G_END_DECLS #endif /* _HAVE_CLUTTER_MAIN_H */ diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 2de3a87cc..d69208a2f 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -69,11 +69,11 @@ typedef struct _ClutterMainContext ClutterMainContext; struct _ClutterMainContext { - ClutterBackend *backend; /* holds a pointer to the windowing - system backend */ - GQueue *events_queue; /* the main event queue */ + ClutterBackend *backend; /* holds a pointer to the windowing + system backend */ + GQueue *events_queue; /* the main event queue */ PangoFT2FontMap *font_map; - guint update_idle; /* repaint idler id */ + guint update_idle; /* repaint idler id */ guint is_initialized : 1; GTimer *timer; /* Used for debugging scheduler */ @@ -85,6 +85,10 @@ struct _ClutterMainContext GHashTable *actor_hash; /* Hash of all actors mapped to id */ guint frame_rate; /* Default FPS */ + + ClutterActor *pointer_grab_actor; /* The actor having the pointer grab + (or NULL if there is no pointer grab) + */ }; #define CLUTTER_CONTEXT() (clutter_context_get_default ()) diff --git a/tests/Makefile.am b/tests/Makefile.am index a06df81f7..b7183aeef 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,7 +1,7 @@ noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \ test-actors test-behave test-text test-entry test-project \ test-boxes test-perspective test-rotate test-depth \ - test-threads test-timeline test-score test-script test-model + test-threads test-timeline test-score test-script test-model test-grab INCLUDES = -I$(top_srcdir)/ LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la @@ -13,6 +13,7 @@ test_events_SOURCES = test-events.c test_offscreen_SOURCES = test-offscreen.c test_scale_SOURCES = test-scale.c test_actors_SOURCES = test-actors.c +test_grab_SOURCES = test-grab.c test_behave_SOURCES = test-behave.c test_text_SOURCES = test-text.c test_entry_SOURCES = test-entry.c diff --git a/tests/test-grab.c b/tests/test-grab.c new file mode 100644 index 000000000..9c5210ec5 --- /dev/null +++ b/tests/test-grab.c @@ -0,0 +1,213 @@ +#include + +static void +stage_state_cb (ClutterStage *stage, + gpointer data) +{ + gchar *detail = (gchar*)data; + + printf("[stage signal] %s\n", detail); +} + +static gboolean +debug_event_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + ClutterStage *stage = CLUTTER_STAGE (clutter_stage_get_default ()); + gchar keybuf[9], *source = (gchar*)data; + int len = 0; + + switch (event->type) + { + case CLUTTER_KEY_PRESS: + len = g_unichar_to_utf8 (clutter_keysym_to_unicode (event->key.keyval), + keybuf); + keybuf[len] = '\0'; + printf ("[%s] KEY PRESS '%s'", source, keybuf); + break; + case CLUTTER_KEY_RELEASE: + len = g_unichar_to_utf8 (clutter_keysym_to_unicode (event->key.keyval), + keybuf); + keybuf[len] = '\0'; + printf ("[%s] KEY RELEASE '%s'", source, keybuf); + break; + case CLUTTER_MOTION: + printf("[%s] MOTION", source); + break; + case CLUTTER_ENTER: + printf("[%s] ENTER", source); + break; + case CLUTTER_LEAVE: + printf("[%s] LEAVE", source); + break; + case CLUTTER_BUTTON_PRESS: + printf("[%s] BUTTON PRESS (click count:%i)", + source, event->button.click_count); + break; + case CLUTTER_BUTTON_RELEASE: + printf("[%s] BUTTON RELEASE", source); + if (clutter_event_get_source (event) == CLUTTER_ACTOR (stage)) + clutter_stage_set_key_focus (stage, NULL); + else if (clutter_event_get_source (event) == actor) + clutter_stage_set_key_focus (stage, actor); + break; + case CLUTTER_SCROLL: + printf("[%s] BUTTON SCROLL", source); + break; + case CLUTTER_STAGE_STATE: + printf("[%s] STAGE STATE", source); + break; + case CLUTTER_DESTROY_NOTIFY: + printf("[%s] DESTROY NOTIFY", source); + break; + case CLUTTER_CLIENT_MESSAGE: + printf("[%s] CLIENT MESSAGE\n", source); + break; + case CLUTTER_DELETE: + printf("[%s] DELETE", source); + break; + case CLUTTER_NOTHING: + return; + } + + if (clutter_event_get_source (event) == actor) + printf(" *source*"); + + printf("\n"); + + return FALSE; +} + +static gboolean +grab_pointer_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + clutter_grab_pointer (actor); + return FALSE; +} + +static gboolean +red_release_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + clutter_ungrab_pointer (); + return FALSE; +} + +static gboolean +blue_release_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + clutter_actor_destroy (actor); + return FALSE; +} + +static gboolean +green_press_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + clutter_enable_motion_events (!clutter_get_motion_events_enabled ()); + g_print ("per actor motion events are now %s\n", + clutter_get_motion_events_enabled ()?"enabled":"disabled"); + return FALSE; +} + +static gboolean +toggle_grab_pointer_cb (ClutterActor *actor, + ClutterEvent *event, + gpointer data) +{ + /* we only deal with the event if the source is ourself */ + if (event->button.source == actor) + { + if (clutter_get_pointer_grab () != NULL) + clutter_ungrab_pointer (); + else + clutter_grab_pointer (actor); + } + return FALSE; +} + + +int +main (int argc, char *argv[]) +{ + ClutterActor *stage, *actor; + ClutterColor rcol = { 0xff, 0, 0, 0xff}, + bcol = { 0, 0, 0xff, 0xff }, + gcol = { 0, 0xff, 0, 0xff }, + ycol = { 0xff, 0xff, 0, 0xff }, + ncol = { 0, 0, 0, 0xff }; + + clutter_init (&argc, &argv); + + g_print ("Red box: aquire grab on press, releases it on next button release\n"); + g_print ("Blue box: aquire grab on press, destroys the blue box actor on release\n"); + g_print ("Yellow box: aquire grab on press, releases grab on next press on yellow box\n"); + g_print ("Green box: toggle per actor motion events.\n\n"); + + stage = clutter_stage_get_default (); + g_signal_connect (stage, "event", G_CALLBACK (debug_event_cb), "stage"); + + g_signal_connect (stage, "fullscreen", + G_CALLBACK (stage_state_cb), "fullscreen"); + g_signal_connect (stage, "unfullscreen", + G_CALLBACK (stage_state_cb), "unfullscreen"); + g_signal_connect (stage, "activate", + G_CALLBACK (stage_state_cb), "activate"); + g_signal_connect (stage, "deactivate", + G_CALLBACK (stage_state_cb), "deactivate"); + + actor = clutter_rectangle_new_with_color (&rcol); + clutter_actor_set_size (actor, 100, 100); + clutter_actor_set_position (actor, 100, 100); + clutter_actor_set_reactive (actor); + clutter_container_add (CLUTTER_CONTAINER (stage), actor, NULL); + g_signal_connect (actor, "event", G_CALLBACK (debug_event_cb), "red box"); + g_signal_connect (actor, "button-press-event", + G_CALLBACK (grab_pointer_cb), NULL); + g_signal_connect (actor, "button-release-event", + G_CALLBACK (red_release_cb), NULL); + + actor = clutter_rectangle_new_with_color (&ycol); + clutter_actor_set_size (actor, 100, 100); + clutter_actor_set_position (actor, 100, 300); + clutter_actor_set_reactive (actor); + clutter_container_add (CLUTTER_CONTAINER (stage), actor, NULL); + g_signal_connect (actor, "event", G_CALLBACK (debug_event_cb), "yellow box"); + g_signal_connect (actor, "button-press-event", + G_CALLBACK (toggle_grab_pointer_cb), NULL); + + actor = clutter_rectangle_new_with_color (&bcol); + clutter_actor_set_size (actor, 100, 100); + clutter_actor_set_position (actor, 300, 100); + clutter_actor_set_reactive (actor); + clutter_container_add (CLUTTER_CONTAINER (stage), actor, NULL); + g_signal_connect (actor, "event", + G_CALLBACK (debug_event_cb), "blue box"); + g_signal_connect (actor, "button-press-event", + G_CALLBACK (grab_pointer_cb), NULL); + g_signal_connect (actor, "button-release-event", + G_CALLBACK (blue_release_cb), NULL); + + actor = clutter_rectangle_new_with_color (&gcol); + clutter_actor_set_size (actor, 100, 100); + clutter_actor_set_position (actor, 300, 300); + clutter_actor_set_reactive (actor); + clutter_container_add (CLUTTER_CONTAINER (stage), actor, NULL); + g_signal_connect (actor, "event", + G_CALLBACK (debug_event_cb), "green box"); + g_signal_connect (actor, "button-press-event", + G_CALLBACK (green_press_cb), NULL); + + clutter_actor_show_all (CLUTTER_ACTOR (stage)); + + clutter_main(); + + return 0; +}