diff --git a/gdk/gdkevents.h b/gdk/gdkevents.h index e6c516c..f3fa26a 100644 --- a/gdk/gdkevents.h +++ b/gdk/gdkevents.h @@ -65,6 +65,11 @@ typedef struct _GdkEventWindowState GdkEventWindowState; typedef struct _GdkEventSetting GdkEventSetting; typedef struct _GdkEventGrabBroken GdkEventGrabBroken; +/* OS X specific gesture events */ +typedef struct _GdkEventGestureMagnify GdkEventGestureMagnify; +typedef struct _GdkEventGestureRotate GdkEventGestureRotate; +typedef struct _GdkEventGestureSwipe GdkEventGestureSwipe; + typedef union _GdkEvent GdkEvent; typedef void (*GdkEventFunc) (GdkEvent *event, @@ -152,6 +157,9 @@ typedef enum GDK_OWNER_CHANGE = 34, GDK_GRAB_BROKEN = 35, GDK_DAMAGE = 36, + GDK_GESTURE_MAGNIFY = 37, + GDK_GESTURE_ROTATE = 38, + GDK_GESTURE_SWIPE = 39, GDK_EVENT_LAST /* helper variable for decls */ } GdkEventType; @@ -488,6 +496,52 @@ struct _GdkEventDND { gshort x_root, y_root; }; +/* Event types for OS X gestures */ + +struct _GdkEventGestureMagnify +{ + GdkEventType type; + GdkWindow *window; + gint8 send_event; + guint32 time; + gdouble x; + gdouble y; + guint state; + gdouble magnification; + GdkDevice *device; + gdouble x_root, y_root; +}; + +struct _GdkEventGestureRotate +{ + GdkEventType type; + GdkWindow *window; + gint8 send_event; + guint32 time; + gdouble x; + gdouble y; + guint state; + gdouble rotation; + GdkDevice *device; + gdouble x_root, y_root; +}; + +struct _GdkEventGestureSwipe +{ + GdkEventType type; + GdkWindow *window; + gint8 send_event; + guint32 time; + gdouble x; + gdouble y; + guint state; + gdouble delta_x; + gdouble delta_y; + GdkDevice *device; + gdouble x_root, y_root; +}; + + union _GdkEvent { GdkEventType type; @@ -511,6 +565,9 @@ union _GdkEvent GdkEventWindowState window_state; GdkEventSetting setting; GdkEventGrabBroken grab_broken; + GdkEventGestureMagnify magnify; + GdkEventGestureRotate rotate; + GdkEventGestureSwipe swipe; }; GType gdk_event_get_type (void) G_GNUC_CONST; diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c index d20b424..fb89451 100644 --- a/gdk/gdkwindow.c +++ b/gdk/gdkwindow.c @@ -9813,6 +9813,9 @@ static const guint type_masks[] = { 0, /* GDK_OWNER_CHANGE = 34 */ 0, /* GDK_GRAB_BROKEN = 35 */ 0, /* GDK_DAMAGE = 36 */ + 0, /* GDK_GESTURE_MAGNIFY = 37 */ + 0, /* GDK_GESTURE_ROTATE = 38 */ + 0, /* GDK_GESTURE_SWIPE = 39 */ }; G_STATIC_ASSERT (G_N_ELEMENTS (type_masks) == GDK_EVENT_LAST); diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index 2c897fb..cdae2f1 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -190,4 +190,37 @@ [self updateTrackingRect]; } +/* Handle OS X gesture events. The Apple documentation is explicit + * that these events should not be captured through a tracking loop (which + * is how we handle usual event handling), so we fallback to overriding + * NSResponder methods. + */ + +-(void)magnifyWithEvent:(NSEvent *)event +{ + GdkEvent *gdk_event; + + gdk_event = _gdk_quartz_events_create_magnify_event (event); + if (gdk_event) + _gdk_event_queue_append (gdk_display_get_default (), gdk_event); +} + +-(void)rotateWithEvent:(NSEvent *)event +{ + GdkEvent *gdk_event; + + gdk_event = _gdk_quartz_events_create_rotate_event (event); + if (gdk_event) + _gdk_event_queue_append (gdk_display_get_default (), gdk_event); +} + +-(void)swipeWithEvent:(NSEvent *)event +{ + GdkEvent *gdk_event; + + gdk_event = _gdk_quartz_events_create_swipe_event (event); + if (gdk_event) + _gdk_event_queue_append (gdk_display_get_default (), gdk_event); +} + @end diff --git a/gdk/quartz/gdkevents-quartz.c b/gdk/quartz/gdkevents-quartz.c index 9478c16..128c794 100644 --- a/gdk/quartz/gdkevents-quartz.c +++ b/gdk/quartz/gdkevents-quartz.c @@ -374,6 +374,11 @@ get_event_mask_from_ns_event (NSEvent *nsevent) case NSMouseExited: return GDK_LEAVE_NOTIFY_MASK; + case NSEventTypeMagnify: + case NSEventTypeRotate: + case NSEventTypeSwipe: + return 0; + default: g_assert_not_reached (); } @@ -752,6 +757,11 @@ find_window_for_ns_event (NSEvent *nsevent, return toplevel; + case NSEventTypeMagnify: + case NSEventTypeRotate: + case NSEventTypeSwipe: + return toplevel; + default: /* Ignore everything else. */ break; @@ -1011,6 +1021,139 @@ fill_key_event (GdkWindow *window, event->key.keyval)); } + +static GdkWindow * +find_window_for_ns_gesture_event (NSEvent *nsevent, + gint *_x, + gint *_y, + gint *x_root, + gint *y_root) +{ + GdkDisplay *display = _gdk_display; + GdkWindow *toplevel_window = NULL, *pointer_window = NULL; + gdouble found_x, found_y; + gint x, y; + + toplevel_window = find_window_for_ns_event (nsevent, &x, &y, x_root, y_root); + if (!toplevel_window) + return NULL; + + if (toplevel_window == display->pointer_info.toplevel_under_pointer) + pointer_window = _gdk_window_find_descendant_at (toplevel_window, + x, y, + &found_x, &found_y); + else + pointer_window = NULL; + + *_x = found_x; + *_y = found_y; + + return pointer_window; +} + + +GdkEvent * +_gdk_quartz_events_create_magnify_event (NSEvent *nsevent) +{ + GdkEvent *event; + GdkWindow *window = NULL; + gint x, y, x_root, y_root; + + window = find_window_for_ns_gesture_event (nsevent, + &x, &y, + &x_root, &y_root); + + if (!window) + return NULL; + + event = gdk_event_new (GDK_GESTURE_MAGNIFY); + + event->any.type = GDK_GESTURE_MAGNIFY; + event->magnify.window = window; + event->magnify.time = get_time_from_ns_event (nsevent); + event->magnify.x = x; + event->magnify.y = y; + event->magnify.x_root = x_root; + event->magnify.y_root = y_root; + event->magnify.magnification = [nsevent magnification]; + event->magnify.state = get_keyboard_modifiers_from_ns_event (nsevent) | + _gdk_quartz_events_get_current_mouse_modifiers (); + event->magnify.device = _gdk_display->core_pointer; + + fixup_event (event); + + return event; +} + +GdkEvent * +_gdk_quartz_events_create_rotate_event (NSEvent *nsevent) +{ + GdkEvent *event; + GdkWindow *window; + gint x, y, x_root, y_root; + + window = find_window_for_ns_gesture_event (nsevent, + &x, &y, + &x_root, &y_root); + + if (!window) + return NULL; + + event = gdk_event_new (GDK_GESTURE_ROTATE); + + event->any.type = GDK_GESTURE_ROTATE; + event->rotate.window = window; + event->rotate.time = get_time_from_ns_event (nsevent); + event->rotate.x = x; + event->rotate.y = y; + event->rotate.x_root = x_root; + event->rotate.y_root = y_root; + event->rotate.rotation = [nsevent rotation]; + event->rotate.state = get_keyboard_modifiers_from_ns_event (nsevent) | + _gdk_quartz_events_get_current_mouse_modifiers (); + event->rotate.device = _gdk_display->core_pointer; + + fixup_event (event); + + return event; +} + +GdkEvent * +_gdk_quartz_events_create_swipe_event (NSEvent *nsevent) +{ + GdkEvent *event; + GdkWindow *window; + gint x, y, x_root, y_root; + + window = find_window_for_ns_gesture_event (nsevent, + &x, &y, + &x_root, &y_root); + + if (!window) + return NULL; + + event = gdk_event_new (GDK_GESTURE_SWIPE); + + event->any.type = GDK_GESTURE_SWIPE; + event->swipe.window = window; + event->swipe.time = get_time_from_ns_event (nsevent); + event->swipe.x = x; + event->swipe.y = y; + event->swipe.x_root = x_root; + event->swipe.y_root = y_root; + event->swipe.delta_x = [nsevent deltaX]; + event->swipe.delta_y = [nsevent deltaY]; + event->swipe.state = get_keyboard_modifiers_from_ns_event (nsevent) | + _gdk_quartz_events_get_current_mouse_modifiers (); + event->swipe.device = _gdk_display->core_pointer; + + fixup_event (event); + + return event; +} + + + static gboolean synthesize_crossing_event (GdkWindow *window, GdkEvent *event, @@ -1392,6 +1535,13 @@ gdk_event_translate (GdkEvent *event, } break; + /* Gesture events are handled through GdkQuartzView, because the + * Apple documentation states very clearly that such events should + * not be handled through a tracking loop (like GDK uses). + * Experiments show that gesture events do not get through our + * tracking loop either. + */ + default: /* Ignore everything elsee. */ return_val = FALSE; diff --git a/gdk/quartz/gdkglobals-quartz.c b/gdk/quartz/gdkglobals-quartz.c index 53c6d5e..6d01b59 100644 --- a/gdk/quartz/gdkglobals-quartz.c +++ b/gdk/quartz/gdkglobals-quartz.c @@ -41,3 +41,9 @@ gdk_quartz_osx_version (void) else return minor; } + +gboolean +gdk_quartz_supports_gesture_events (void) +{ + return TRUE; +} diff --git a/gdk/quartz/gdkprivate-quartz.h b/gdk/quartz/gdkprivate-quartz.h index c1b3083..03a3857 100644 --- a/gdk/quartz/gdkprivate-quartz.h +++ b/gdk/quartz/gdkprivate-quartz.h @@ -179,6 +179,10 @@ GdkModifierType _gdk_quartz_events_get_current_mouse_modifiers (void); void _gdk_quartz_events_send_enter_notify_event (GdkWindow *window); void _gdk_quartz_events_break_all_grabs (guint32 time); +GdkEvent *_gdk_quartz_events_create_magnify_event (NSEvent *event); +GdkEvent *_gdk_quartz_events_create_rotate_event (NSEvent *event); +GdkEvent *_gdk_quartz_events_create_swipe_event (NSEvent *event); + /* Event loop */ gboolean _gdk_quartz_event_loop_check_pending (void); NSEvent * _gdk_quartz_event_loop_get_pending (void); diff --git a/gdk/quartz/gdkquartz.h b/gdk/quartz/gdkquartz.h index 742d651..f79d04a 100644 --- a/gdk/quartz/gdkquartz.h +++ b/gdk/quartz/gdkquartz.h @@ -57,6 +57,7 @@ NSImage *gdk_quartz_pixbuf_to_ns_image_libgtk_only (GdkPixbuf id gdk_quartz_drag_context_get_dragging_info_libgtk_only (GdkDragContext *context); NSEvent *gdk_quartz_event_get_nsevent (GdkEvent *event); GdkOSXVersion gdk_quartz_osx_version (void); +gboolean gdk_quartz_supports_gesture_events (void); G_END_DECLS diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index 5a88679..db77675 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -1636,6 +1636,9 @@ gtk_main_do_event (GdkEvent *event) case GDK_WINDOW_STATE: case GDK_GRAB_BROKEN: case GDK_DAMAGE: + case GDK_GESTURE_MAGNIFY: + case GDK_GESTURE_ROTATE: + case GDK_GESTURE_SWIPE: if (!_gtk_widget_captured_event (event_widget, event)) gtk_widget_event (event_widget, event); break; diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 9a43d27..2d11e09 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -197,6 +197,9 @@ enum { KEYNAV_FAILED, DRAG_FAILED, DAMAGE_EVENT, + GESTURE_MAGNIFY_EVENT, + GESTURE_ROTATE_EVENT, + GESTURE_SWIPE_EVENT, LAST_SIGNAL }; @@ -2242,6 +2245,70 @@ gtk_widget_class_init (GtkWidgetClass *klass) _gtk_marshal_BOOLEAN__BOXED, G_TYPE_BOOLEAN, 1, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + /** + * GtkWidget::gesture-magnify-event: + * @widget: the object which received the signal + * @event: the #GdkEventGestureMagnify event + * + * Emitted when a magnify event is received on @widget's window. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + * + * Since: 2.24 Xamarin specific. + */ + widget_signals[GESTURE_MAGNIFY_EVENT] = + g_signal_new (I_("gesture-magnify-event"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + /** + * GtkWidget::gesture-rotate-event: + * @widget: the object which received the signal + * @event: the #GdkEventGestureRotate event + * + * Emitted when a rotation event is received on @widget's window. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + * + * Since: 2.24 Xamarin specific. + */ + widget_signals[GESTURE_ROTATE_EVENT] = + g_signal_new (I_("gesture-rotate-event"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + /** + * GtkWidget::gesture-swipe-event: + * @widget: the object which received the signal + * @event: the #GdkEventGestureSwipe event + * + * Emitted when a swipe event is received on @widget's window. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + * + * Since: 2.24 Xamarin specific. + */ + widget_signals[GESTURE_SWIPE_EVENT] = + g_signal_new (I_("gesture-swipe-event"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + /** * GtkWidget::grab-broken-event: * @widget: the object which received the signal @@ -4975,6 +5042,15 @@ gtk_widget_event_internal (GtkWidget *widget, case GDK_DAMAGE: signal_num = DAMAGE_EVENT; break; + case GDK_GESTURE_MAGNIFY: + signal_num = GESTURE_MAGNIFY_EVENT; + break; + case GDK_GESTURE_ROTATE: + signal_num = GESTURE_ROTATE_EVENT; + break; + case GDK_GESTURE_SWIPE: + signal_num = GESTURE_SWIPE_EVENT; + break; default: g_warning ("gtk_widget_event(): unhandled event type: %d", event->type); signal_num = -1; diff --git a/tests/Makefile.am b/tests/Makefile.am index 3888826..68a0a79 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -88,7 +88,8 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testactions \ testgrouping \ testtooltips \ - testvolumebutton + testvolumebutton \ + testgestures if HAVE_CXX noinst_PROGRAMS += autotestkeywords @@ -165,6 +166,7 @@ testgrouping_DEPENDENCIES = $(TEST_DEPS) testtooltips_DEPENDENCIES = $(TEST_DEPS) testvolumebutton_DEPENDENCIES = $(TEST_DEPS) testwindows_DEPENDENCIES = $(TEST_DEPS) +testgestures_DEPENDENCIES = $(TEST_DEPS) testentrycompletion_SOURCES = \ prop-editor.c \ @@ -273,6 +275,9 @@ testoffscreenwindow_SOURCES = \ testwindows_SOURCES = \ testwindows.c +testgestures_SOURCES = \ + testgestures.c + EXTRA_DIST += \ prop-editor.h \ testgtk.1 \ diff --git a/tests/testgestures.c b/tests/testgestures.c new file mode 100644 index 0000000..ae3ab99 --- /dev/null +++ b/tests/testgestures.c @@ -0,0 +1,214 @@ +#include +#include + + +typedef struct +{ + gdouble width; + gdouble height; + + gdouble angle; + + GtkWidget *widget; + gint offset_x; + gint offset_y; + gdouble progress; + gboolean increasing; +} +RectangleInfo; + + + +static gboolean +handle_expose_event (GtkWidget *widget, + GdkEventExpose *expose, + gpointer user_data) +{ + cairo_t *cr; + int center_x, center_y; + RectangleInfo *rect = (RectangleInfo *)user_data; + + cr = gdk_cairo_create (widget->window); + + cairo_save (cr); + + /* Background */ + cairo_rectangle (cr, 0, 0, + widget->allocation.width, + widget->allocation.height); + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_fill (cr); + + cairo_restore (cr); + + /* Rectangle */ + center_x = (widget->allocation.width - rect->width) / 2; + center_y = (widget->allocation.height - rect->height) / 2; + + if (rect->progress != 0.0f) + { + cairo_translate (cr, rect->offset_x * rect->progress, + rect->offset_y * rect->progress); + } + + cairo_save (cr); + + cairo_translate (cr, widget->allocation.width / 2, + widget->allocation.height / 2); + cairo_rotate (cr, rect->angle * M_PI / 180.0); + cairo_translate (cr, -widget->allocation.width / 2, + -widget->allocation.height / 2); + + + cairo_rectangle (cr, + center_x, center_y, + rect->width, rect->height); + cairo_set_source_rgb (cr, 0.9, 0.0, 0.0); + cairo_stroke (cr); + + cairo_rectangle (cr, + center_x, center_y, + rect->width, rect->height); + cairo_set_source_rgba (cr, 0.9, 0.0, 0.0, 0.3); + cairo_fill (cr); + + cairo_restore (cr); + + + cairo_destroy (cr); + + return FALSE; +} + +static gboolean +handle_gesture_magnify_event (GtkWidget *widget, + GdkEventGestureMagnify *magnify, + gpointer user_data) +{ + RectangleInfo *rect = (RectangleInfo *)user_data; + + rect->width += rect->width * magnify->magnification; + if (rect->width < 5) + rect->width = 5; + + rect->height += rect->height * magnify->magnification; + if (rect->height < 5) + rect->height = 5; + + gtk_widget_queue_draw (widget); + + return TRUE; +} + +static gboolean +handle_gesture_rotate_event (GtkWidget *widget, + GdkEventGestureRotate *rotate, + gpointer user_data) +{ + RectangleInfo *rect = (RectangleInfo *)user_data; + + rect->angle -= rotate->rotation; + + gtk_widget_queue_draw (widget); + + return TRUE; +} + + +static gboolean +bounce_timeout (gpointer user_data) +{ + gboolean retval = TRUE; + RectangleInfo *rect = (RectangleInfo *)user_data; + + if (rect->increasing) + rect->progress += 0.10f; + else + rect->progress -= 0.10f; + + if (rect->progress > 1.0f) + { + rect->progress = 0.90f; + rect->increasing = FALSE; + } + else if (rect->progress <= 0.0f) + { + rect->progress = 0.0f; + retval = FALSE; + } + + gtk_widget_queue_draw (rect->widget); + + return retval; +} + +static void +bounce (RectangleInfo *rect, + int offset_x, + int offset_y) +{ + if (rect->progress != 0.0f) + return; + + rect->progress = 0.10f; + rect->increasing = TRUE; + rect->offset_x = offset_x; + rect->offset_y = offset_y; + gtk_widget_queue_draw (rect->widget); + + gdk_threads_add_timeout (25, bounce_timeout, rect); +} + +static gboolean +handle_gesture_swipe_event (GtkWidget *widget, + GdkEventGestureSwipe *swipe, + gpointer user_data) +{ + int offset_x = 150, offset_y = 150; + RectangleInfo *rect = (RectangleInfo *)user_data; + + offset_x *= -1.0 * swipe->delta_x; + offset_y *= -1.0 * swipe->delta_y; + + bounce (rect, offset_x, offset_y); + + return TRUE; +} + +int +main (int argc, char **argv) +{ + GtkWidget *window; + GtkWidget *drawing_area; + RectangleInfo rect; + + gtk_init (&argc, &argv); + + rect.width = 40.0; + rect.height = 40.0; + rect.angle = 0.0; + rect.progress = 0.0f; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + g_signal_connect (window, "delete-event", + G_CALLBACK (gtk_main_quit), NULL); + + drawing_area = gtk_drawing_area_new (); + rect.widget = drawing_area; + g_signal_connect (drawing_area, "expose-event", + G_CALLBACK (handle_expose_event), &rect); + g_signal_connect (drawing_area, "gesture-magnify-event", + G_CALLBACK (handle_gesture_magnify_event), &rect); + g_signal_connect (drawing_area, "gesture-rotate-event", + G_CALLBACK (handle_gesture_rotate_event), &rect); + g_signal_connect (drawing_area, "gesture-swipe-event", + G_CALLBACK (handle_gesture_swipe_event), &rect); + gtk_container_add (GTK_CONTAINER (window), drawing_area); + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +}