From 79adbda38f9ca25d351929bb46b9bc3796a7afc6 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Tue, 21 Feb 2012 11:29:13 +0100 Subject: paned: Use 2-finger interaction to resize the panes If 2 touches happen to be both at left and right of the handle, enter into resize mode. Else, the event(s) will be propagated further to the children. --- gtk/gtkpaned.c | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 331 insertions(+), 27 deletions(-) diff --git a/gtk/gtkpaned.c b/gtk/gtkpaned.c index fabbc25d7f..0141710bc6 100644 --- a/gtk/gtkpaned.c +++ b/gtk/gtkpaned.c @@ -34,6 +34,8 @@ #include "gtktypebuiltins.h" #include "gtkprivate.h" #include "gtkintl.h" +#include "gtkdnd.h" +#include "gtkwidgetprivate.h" #include "a11y/gtkpanedaccessible.h" /** @@ -96,6 +98,17 @@ enum { CHILD2 }; +typedef struct +{ + GdkWindow *pane_window; + GdkDevice *device; + GdkEventSequence *sequence; + gdouble x; + gdouble y; + GdkEvent *button_press_event; + guint press_consumed : 1; +} TouchInfo; + struct _GtkPanedPrivate { GtkPaned *first_paned; @@ -122,6 +135,8 @@ struct _GtkPanedPrivate guint32 grab_time; + GArray *touches; + guint handle_prelit : 1; guint in_drag : 1; guint in_recursion : 1; @@ -253,6 +268,8 @@ static gboolean gtk_paned_toggle_handle_focus (GtkPaned *paned); static GType gtk_paned_child_type (GtkContainer *container); static void gtk_paned_grab_notify (GtkWidget *widget, gboolean was_grabbed); +static gboolean gtk_paned_captured_event (GtkWidget *widget, + GdkEvent *event); G_DEFINE_TYPE_WITH_CODE (GtkPaned, gtk_paned, GTK_TYPE_CONTAINER, @@ -706,6 +723,11 @@ gtk_paned_init (GtkPaned *paned) priv->handle_pos.y = -1; priv->drag_pos = -1; + + priv->touches = g_array_sized_new (FALSE, FALSE, + sizeof (TouchInfo), 2); + + _gtk_widget_set_captured_event_handler (GTK_WIDGET (paned), gtk_paned_captured_event); } static void @@ -861,9 +883,11 @@ static void gtk_paned_finalize (GObject *object) { GtkPaned *paned = GTK_PANED (object); - + GtkPanedPrivate *priv = paned->priv; + gtk_paned_set_saved_focus (paned, NULL); gtk_paned_set_first_paned (paned, NULL); + g_array_free (priv->touches, TRUE); G_OBJECT_CLASS (gtk_paned_parent_class)->finalize (object); } @@ -1279,7 +1303,9 @@ gtk_paned_create_child_window (GtkPaned *paned, attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; - attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_TOUCH_MASK; + if (child) { GtkAllocation allocation; @@ -1569,7 +1595,8 @@ gtk_paned_enter (GtkWidget *widget, GtkPaned *paned = GTK_PANED (widget); GtkPanedPrivate *priv = paned->priv; - if (priv->in_drag) + if (priv->in_drag && + priv->touches->len == 0) update_drag (paned, event->x, event->y); else { @@ -1591,7 +1618,8 @@ gtk_paned_leave (GtkWidget *widget, GtkPaned *paned = GTK_PANED (widget); GtkPanedPrivate *priv = paned->priv; - if (priv->in_drag) + if (priv->in_drag && + priv->touches->len == 0) update_drag (paned, event->x, event->y); else { @@ -1624,6 +1652,38 @@ gtk_paned_focus (GtkWidget *widget, return retval; } +static gboolean +start_drag (GtkPaned *paned, + GdkDevice *device, + int xpos, + int ypos, + guint time) +{ + GtkPanedPrivate *priv = paned->priv; + + if (gdk_device_grab (device, + priv->handle, + GDK_OWNERSHIP_WINDOW, FALSE, + GDK_BUTTON1_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_TOUCH_MASK, + NULL, time) != GDK_GRAB_SUCCESS) + return FALSE; + + priv->in_drag = TRUE; + priv->grab_time = time; + priv->grab_device = device; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + priv->drag_pos = xpos; + else + priv->drag_pos = ypos; + + return TRUE; +} + static gboolean gtk_paned_button_press (GtkWidget *widget, GdkEventButton *event) @@ -1634,27 +1694,10 @@ gtk_paned_button_press (GtkWidget *widget, if (!priv->in_drag && (event->window == priv->handle) && (event->button == GDK_BUTTON_PRIMARY)) { - /* We need a server grab here, not gtk_grab_add(), since - * we don't want to pass events on to the widget's children */ - if (gdk_device_grab (event->device, - priv->handle, - GDK_OWNERSHIP_WINDOW, FALSE, - GDK_BUTTON1_MOTION_MASK - | GDK_BUTTON_RELEASE_MASK - | GDK_ENTER_NOTIFY_MASK - | GDK_LEAVE_NOTIFY_MASK, - NULL, event->time) != GDK_GRAB_SUCCESS) + if (!start_drag (paned, event->device, + event->x, event->y, event->time)) return FALSE; - priv->in_drag = TRUE; - priv->grab_time = event->time; - priv->grab_device = event->device; - - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - priv->drag_pos = event->x; - else - priv->drag_pos = event->y; - return TRUE; } @@ -1668,9 +1711,13 @@ gtk_paned_grab_broken (GtkWidget *widget, GtkPaned *paned = GTK_PANED (widget); GtkPanedPrivate *priv = paned->priv; - priv->in_drag = FALSE; - priv->drag_pos = -1; - priv->position_set = TRUE; + if (event->grab_window != priv->handle && + gdk_event_get_device ((GdkEvent *) event) == priv->grab_device) + { + priv->in_drag = FALSE; + priv->drag_pos = -1; + priv->position_set = TRUE; + } return TRUE; } @@ -1685,7 +1732,7 @@ stop_drag (GtkPaned *paned) priv->position_set = TRUE; gdk_device_ungrab (priv->grab_device, - priv->grab_time); + gtk_get_current_event_time ()); priv->grab_device = NULL; } @@ -1760,6 +1807,263 @@ gtk_paned_motion (GtkWidget *widget, return FALSE; } +static GdkWindow * +_gtk_paned_find_pane_window (GtkWidget *widget, + GdkEvent *event) +{ + GtkPanedPrivate *priv = GTK_PANED (widget)->priv; + GtkWidget *event_widget; + + event_widget = gtk_get_event_widget (event); + + if (event_widget == widget) + { + if (event->any.window == priv->child1_window) + return priv->child1_window; + else if (event->any.window == priv->child2_window) + return priv->child2_window; + + return NULL; + } + else if (event_widget == priv->child1 || + gtk_widget_is_ancestor (event_widget, priv->child1)) + return priv->child1_window; + else if (event_widget == priv->child2 || + gtk_widget_is_ancestor (event_widget, priv->child2)) + return priv->child2_window; + + return NULL; +} + +static TouchInfo * +_gtk_paned_find_touch (GtkPaned *paned, + GdkDevice *device, + GdkEventSequence *sequence, + guint *index) +{ + GtkPanedPrivate *priv = paned->priv; + TouchInfo *info; + guint i; + + for (i = 0; i < priv->touches->len; i++) + { + info = &g_array_index (priv->touches, TouchInfo, i); + + if (info->device == device && info->sequence == sequence) + { + if (index) + *index = i; + + return info; + } + } + + return NULL; +} + +static void +gtk_paned_release_captured_event (GtkPaned *paned, + TouchInfo *touch, + gboolean emit) +{ + GtkPanedPrivate *priv = paned->priv; + GtkWidget *event_widget, *child; + + if (!touch->button_press_event) + return; + + event_widget = gtk_get_event_widget (touch->button_press_event); + + if (touch->pane_window == priv->child1_window) + child = priv->child1; + else if (touch->pane_window == priv->child2_window) + child = priv->child2; + else + return; + + if (emit && + !_gtk_propagate_captured_event (event_widget, + touch->button_press_event, + child)) + gtk_propagate_event (event_widget, touch->button_press_event); + + gdk_event_free (touch->button_press_event); + touch->button_press_event = NULL; +} + +static gboolean +gtk_paned_captured_event (GtkWidget *widget, + GdkEvent *event) +{ + GtkPaned *paned = GTK_PANED (widget); + GtkPanedPrivate *priv = paned->priv; + GdkDevice *device, *source_device; + GdkWindow *pane_window; + TouchInfo new = { 0 }, *info; + GdkEventSequence *sequence; + guint index; + gdouble event_x, event_y; + gint x, y; + + device = gdk_event_get_device (event); + source_device = gdk_event_get_source_device (event); + + /* We possibly deal with both pointer and touch events, + * depending on the target window event mask, so assume + * touch ID = 0 for pointer events to ease handling. + */ + sequence = gdk_event_get_event_sequence (event); + + gdk_event_get_coords (event, &event_x, &event_y); + + if (!source_device || + gdk_device_get_source (source_device) != GDK_SOURCE_TOUCHSCREEN) + return FALSE; + + switch (event->type) + { + case GDK_BUTTON_PRESS: + case GDK_TOUCH_BEGIN: + if (priv->touches->len == 2) + return FALSE; + + pane_window = _gtk_paned_find_pane_window (widget, event); + gtk_widget_translate_coordinates (gtk_get_event_widget (event), + widget, + (gint)event_x, (gint)event_y, + &x, &y); + + for (index = 0; index < priv->touches->len; index++) + { + info = &g_array_index (priv->touches, TouchInfo, index); + + /* There was already a touch in this pane */ + if (info->pane_window == pane_window) + return FALSE; + } + + new.device = device; + new.sequence = sequence; + new.pane_window = pane_window; + new.x = x; + new.y = y; + g_array_append_val (priv->touches, new); + + if (priv->touches->len == 1) + { + /* It's the first touch, store the event and set + * a timeout in order to release the event if no + * second touch happens timely. + */ + info = &g_array_index (priv->touches, TouchInfo, 0); + info->button_press_event = gdk_event_copy (event); + return TRUE; + } + else if (priv->touches->len == 2) + { + GtkWidget *event_widget; + + /* It's the second touch, release (don't emit) the + * held button/touch presses. + */ + for (index = 0; index < priv->touches->len; index++) + { + info = &g_array_index (priv->touches, TouchInfo, index); + gtk_paned_release_captured_event (paned, info, FALSE); + info->press_consumed = TRUE; + } + + event_widget = gtk_get_event_widget (event); + + if (event_widget == widget) + { + if (pane_window == priv->child2_window) + { + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + event_x += priv->handle_pos.x + priv->handle_pos.width; + else + event_y += priv->handle_pos.y + priv->handle_pos.height; + } + } + else + gtk_widget_translate_coordinates (event_widget, widget, + (gint)event_x, (gint)event_y, + &x, &y); + + start_drag (paned, device, x, y, + event->button.time); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + priv->drag_pos = x - priv->handle_pos.x; + else + priv->drag_pos = y - priv->handle_pos.y; + + return TRUE; + } + + break; + case GDK_BUTTON_RELEASE: + case GDK_TOUCH_END: + info = _gtk_paned_find_touch (GTK_PANED (widget), device, sequence, &index); + + if (info) + { + gboolean press_consumed; + + if (priv->touches->len == 2) + stop_drag (paned); + + press_consumed = info->press_consumed; + + /* Release the held button/touch press, if still queued */ + gtk_paned_release_captured_event (paned, info, TRUE); + g_array_remove_index_fast (priv->touches, index); + + if (press_consumed) + return TRUE; + } + break; + case GDK_MOTION_NOTIFY: + case GDK_TOUCH_UPDATE: + info = _gtk_paned_find_touch (GTK_PANED (widget), device, sequence, &index); + + if (info) + { + gtk_widget_translate_coordinates (gtk_get_event_widget (event), + widget, + event_x, event_y, + &x, &y); + + /* If there is a single touch and this isn't a continuation + * from a previous successful 2-touch operation, check + * the threshold to let the child handle events. + */ + if (priv->touches->len == 1 && + gtk_drag_check_threshold (widget, info->x, info->y, x, y)) + { + gtk_paned_release_captured_event (paned, info, TRUE); + g_array_remove_index_fast (priv->touches, index); + return FALSE; + } + else if (priv->touches->len == 2 && index == 1) + { + /* The device grab on priv->handle is in effect now, + * so the event coordinates are already relative to + * that window. + */ + update_drag (paned, event_x, event_y); + } + + return TRUE; + } + break; + default: + break; + } + + return FALSE; +} + /** * gtk_paned_new: * @orientation: the paned's orientation. -- cgit v1.2.1