Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gestures.c

  Sprache: C
 

#include <gtk/gtk.h>

#define GTK_COMPILATION
#include "gdk/gdkeventsprivate.h"

typedef struct {
  GtkWidget *widget;
  int x;
  int y;
  guint state;
  guint pressed : 1;
} PointState;

static PointState mouse_state;
static PointState touch_state[10]; /* touchpoint 0 gets pointer emulation,
                                    * use it first in tests for consistency.
                                    */


#define EVENT_SEQUENCE(point) (GdkEventSequence*) ((point) - touch_state + 1)

static void
show_toplevel (GtkWindow *window)
{
  GdkSurface *surface;

  gtk_window_present (window);

  surface = gtk_native_get_surface (GTK_NATIVE (window));

  while (gdk_surface_get_width (surface) <= 1 ||
         gdk_surface_get_height (surface) <= 1)
    g_main_context_iteration (NULL, TRUE);
}

static void
show_popover (GtkPopover *popover)
{
  GdkSurface *surface;

  gtk_popover_popup (popover);

  surface = gtk_native_get_surface (GTK_NATIVE (popover));

  while (gdk_surface_get_width (surface) <= 1 ||
         gdk_surface_get_height (surface) <= 1)
    g_main_context_iteration (NULL, TRUE);
}

static void
inject_event (GdkEvent *event)
{
  gboolean handled;

  g_signal_emit_by_name (event->surface, "event", event, &handled);
}

static void
point_press (PointState *point,
             GtkWidget  *widget,
             guint       button)
{
  GdkDisplay *display;
  GdkDevice *device;
  GdkSeat *seat;
  GdkSurface *surface;
  GdkEvent *ev;

  display = gtk_widget_get_display (widget);
  seat = gdk_display_get_default_seat (display);
  device = gdk_seat_get_pointer (seat);
  surface = gtk_native_get_surface (gtk_widget_get_native (widget));

  if (point == &mouse_state)
    {
      GdkButtonEvent *button_event;

      button_event = (GdkButtonEvent *) g_type_create_instance (GDK_TYPE_BUTTON_EVENT);
      ev = (GdkEvent *) button_event;

      ev->event_type = GDK_BUTTON_PRESS;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      button_event->button = button;
      button_event->state = point->state;
      button_event->x = point->x;
      button_event->y = point->y;

      point->state |= GDK_BUTTON1_MASK << (button - 1);
    }
  else
    {
      GdkTouchEvent *touch_event;

      touch_event = (GdkTouchEvent *) g_type_create_instance (GDK_TYPE_TOUCH_EVENT);
      ev = (GdkEvent *) touch_event;

      ev->event_type = GDK_TOUCH_BEGIN;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      touch_event->sequence = EVENT_SEQUENCE (point);
      touch_event->state = point->state;
      touch_event->x = point->x;
      touch_event->y = point->y;
      touch_event->touch_emulating = (point == &touch_state[0]);
    }

  inject_event (ev);

  gdk_event_unref (ev);

  point->widget = widget;
}

static void
point_update (PointState *point,
              GtkWidget  *widget,
              double      x,
              double      y)
{
  GdkDisplay *display;
  GdkDevice *device;
  GdkSeat *seat;
  GdkSurface *surface;
  GdkEvent *ev;

  display = gtk_widget_get_display (widget);
  seat = gdk_display_get_default_seat (display);
  device = gdk_seat_get_pointer (seat);
  surface = gtk_native_get_surface (gtk_widget_get_native (widget));

  point->x = x;
  point->y = y;

  if (point == &mouse_state)
    {
      GdkMotionEvent *motion_event;

      motion_event = (GdkMotionEvent *) g_type_create_instance (GDK_TYPE_MOTION_EVENT);
      ev = (GdkEvent *) motion_event;

      ev->event_type = GDK_MOTION_NOTIFY;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      motion_event->state = point->state;
      motion_event->x = point->x;
      motion_event->y = point->y;
    }
  else
    {
      GdkTouchEvent *touch_event;

      if (!point->widget || widget != point->widget)
        return;

      touch_event = (GdkTouchEvent *) g_type_create_instance (GDK_TYPE_TOUCH_EVENT);
      ev = (GdkEvent *) touch_event;

      ev->event_type = GDK_TOUCH_UPDATE;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      touch_event->sequence = EVENT_SEQUENCE (point);
      touch_event->state = point->state;
      touch_event->x = point->x;
      touch_event->y = point->y;
      touch_event->touch_emulating = (point == &touch_state[0]);
    }

  inject_event (ev);

  gdk_event_unref (ev);
}

static void
point_release (PointState *point,
               guint       button)
{
  GdkDisplay *display;
  GdkDevice *device;
  GdkSeat *seat;
  GdkSurface *surface;
  GdkEvent *ev;

  if (point->widget == NULL)
    return;

  display = gtk_widget_get_display (point->widget);
  seat = gdk_display_get_default_seat (display);
  device = gdk_seat_get_pointer (seat);
  surface = gtk_native_get_surface (gtk_widget_get_native (point->widget));

  if (!point->widget)
    return;

  if (point == &mouse_state)
    {
      GdkButtonEvent *button_event;

      if ((point->state & (GDK_BUTTON1_MASK << (button - 1))) == 0)
        return;

      button_event = (GdkButtonEvent *) g_type_create_instance (GDK_TYPE_BUTTON_EVENT);
      ev = (GdkEvent *) button_event;

      ev->event_type = GDK_BUTTON_RELEASE;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      button_event->button = button;
      button_event->state = point->state;
      button_event->x = point->x;
      button_event->y = point->y;

      point->state &= ~(GDK_BUTTON1_MASK << (button - 1));
    }
  else
    {
      GdkTouchEvent *touch_event;

      touch_event = (GdkTouchEvent *) g_type_create_instance (GDK_TYPE_TOUCH_EVENT);
      ev = (GdkEvent *) touch_event;

      ev->event_type = GDK_TOUCH_END;
      ev->surface = g_object_ref (surface);
      ev->device = g_object_ref (device);
      ev->time = GDK_CURRENT_TIME;

      touch_event->sequence = EVENT_SEQUENCE (point);
      touch_event->state = point->state;
      touch_event->x = point->x;
      touch_event->y = point->y;
      touch_event->touch_emulating = (point == &touch_state[0]);
    }

  inject_event (ev);

  gdk_event_unref (ev);
}

static const char *
phase_nick (GtkPropagationPhase phase)
{
 GTypeClass *class;
 GEnumValue *value;

 class = g_type_class_ref (GTK_TYPE_PROPAGATION_PHASE);
 value = g_enum_get_value ((GEnumClass*)class, phase);
 g_type_class_unref (class);

 return value->value_nick;
}

static const char *
state_nick (GtkEventSequenceState state)
{
 GTypeClass *class;
 GEnumValue *value;

 class = g_type_class_ref (GTK_TYPE_EVENT_SEQUENCE_STATE);
 value = g_enum_get_value ((GEnumClass*)class, state);
 g_type_class_unref (class);

 return value->value_nick;
}

typedef struct {
  GtkEventController *controller;
  GString *str;
  gboolean exit;
} LegacyData;

static gboolean
legacy_cb (GtkEventControllerLegacy *c, GdkEvent *button, gpointer data)
{
  if (gdk_event_get_event_type (button) == GDK_BUTTON_PRESS)
    {
      LegacyData *ld = data;
      GtkWidget *w;

      w = gtk_event_controller_get_widget (ld->controller);

      if (ld->str->len > 0)
        g_string_append (ld->str, ", ");
      g_string_append_printf (ld->str, "legacy %s", gtk_widget_get_name (w));

      return ld->exit;
    }

  return GDK_EVENT_PROPAGATE;
}

typedef struct {
  GString *str;
  GtkEventSequenceState state;
} GestureData;

static void
press_cb (GtkGesture *g, int n_press, double x, double y, gpointer data)
{
  GtkEventController *c = GTK_EVENT_CONTROLLER (g);
  GdkEventSequence *sequence;
  GtkPropagationPhase phase;
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");
  phase = gtk_event_controller_get_propagation_phase (c);

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s %s", phase_nick (phase), name);

  sequence = gtk_gesture_get_last_updated_sequence (g);

  if (sequence)
    g_string_append_printf (gd->str, " (%x)", GPOINTER_TO_UINT (sequence));

  if (gd->state != GTK_EVENT_SEQUENCE_NONE)
    gtk_gesture_set_state (g, gd->state);
}

static void
cancel_cb (GtkGesture *g, GdkEventSequence *sequence, gpointer data)
{
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s cancelled", name);
}

static void
begin_cb (GtkGesture *g, GdkEventSequence *sequence, gpointer data)
{
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s began", name);

  if (gd->state != GTK_EVENT_SEQUENCE_NONE)
    gtk_gesture_set_state (g, gd->state);
}

static void
end_cb (GtkGesture *g, GdkEventSequence *sequence, gpointer data)
{
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s ended", name);
}

static void
update_cb (GtkGesture *g, GdkEventSequence *sequence, gpointer data)
{
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s updated", name);
}

static void
state_changed_cb (GtkGesture *g, GdkEventSequence *sequence, GtkEventSequenceState state, gpointer data)
{
  GestureData *gd = data;
  const char *name;

  name = g_object_get_data (G_OBJECT (g), "name");

  if (gd->str->len > 0)
    g_string_append (gd->str, ", ");
  g_string_append_printf (gd->str, "%s state %s", name, state_nick (state));

  if (sequence != NULL)
    g_string_append_printf (gd->str, " (%x)", GPOINTER_TO_UINT (sequence));
}


static GtkGesture *
add_gesture (GtkWidget *w, const char *name, GtkPropagationPhase phase, GString *str, GtkEventSequenceState state)
{
  GtkGesture *g;
  GestureData *data;
  GList *gestures;

  data = g_new (GestureData, 1);
  data->str = str;
  data->state = state;

  g = gtk_gesture_click_new ();
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (g), FALSE);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (g), 1);
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (g), phase);
  gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (g));

  g_object_set_data (G_OBJECT (g), "name", (gpointer)name);

  gestures = g_object_steal_data (G_OBJECT (w), "gestures");
  gestures = g_list_prepend (gestures, g);
  g_object_set_data_full (G_OBJECT (w), "gestures", (gpointer) gestures,
                          (GDestroyNotify) g_list_free);

  g_signal_connect (g, "pressed", G_CALLBACK (press_cb), data);
  g_signal_connect (g, "cancel", G_CALLBACK (cancel_cb), data);
  g_signal_connect (g, "update", G_CALLBACK (update_cb), data);
  g_signal_connect (g, "sequence-state-changed", G_CALLBACK (state_changed_cb), data);

  return g;
}

static GtkGesture *
add_mt_gesture (GtkWidget *w, const char *name, GtkPropagationPhase phase, GString *str, GtkEventSequenceState state)
{
  GtkGesture *g;
  GestureData *data;
  GList *gestures;

  data = g_new (GestureData, 1);
  data->str = str;
  data->state = state;

  g = gtk_gesture_rotate_new ();
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (g), phase);
  gtk_widget_add_controller (w, GTK_EVENT_CONTROLLER (g));

  g_object_set_data (G_OBJECT (g), "name", (gpointer)name);

  gestures = g_object_get_data (G_OBJECT (w), "gestures");
  gestures = g_list_prepend (gestures, g);
  g_object_set_data_full (G_OBJECT (w), "gestures", (gpointer) gestures,
                          (GDestroyNotify) g_list_free);

  g_signal_connect (g, "begin", G_CALLBACK (begin_cb), data);
  g_signal_connect (g, "update", G_CALLBACK (update_cb), data);
  g_signal_connect (g, "end", G_CALLBACK (end_cb), data);
  g_signal_connect (g, "sequence-state-changed", G_CALLBACK (state_changed_cb), data);

  return g;
}

static void
add_legacy (GtkWidget *w, GString *str, gboolean exit)
{
  LegacyData *data;

  data = g_new (LegacyData, 1);
  data->controller = gtk_event_controller_legacy_new ();
  data->str = str;
  data->exit = exit;

  gtk_event_controller_set_propagation_phase (data->controller, GTK_PHASE_BUBBLE);
  gtk_widget_add_controller (w, data->controller);
  g_signal_connect (data->controller, "event", G_CALLBACK (legacy_cb), data);
}

static void
assert_gesture_state (GtkWidget *w, const char *name, GdkEventSequence *seq, GtkEventSequenceState state)
{
  GList *gestures, *l;

  gestures = g_object_get_data (G_OBJECT (w), "gestures");

  for (l = gestures; l; l = l->next)
    {
      const char *n;

      n = g_object_get_data (l->data, "name");
      if (g_strcmp0 (n, name) != 0)
        continue;

      g_assert_cmpint (gtk_gesture_get_sequence_state (l->data, seq), ==, state);
      return;
    }

  g_assert_not_reached ();
}

static void
test_phases (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "bubble c3, "
                   "bubble b3, "
                   "bubble a3");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_mixed (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_legacy (A, str, GDK_EVENT_PROPAGATE);
  add_legacy (B, str, GDK_EVENT_PROPAGATE);
  add_legacy (C, str, GDK_EVENT_PROPAGATE);

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "bubble c3, "
                   "legacy C, "
                   "bubble b3, "
                   "legacy B, "
                   "bubble a3, "
                   "legacy A");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_early_exit (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_legacy (A, str, GDK_EVENT_PROPAGATE);
  add_legacy (B, str, GDK_EVENT_STOP);
  add_legacy (C, str, GDK_EVENT_PROPAGATE);

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "bubble c3, "
                   "legacy C, "
                   "bubble b3, "
                   "legacy B");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_claim_capture (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "b1 cancelled, "
                   "a1 cancelled, "
                   "c1 state claimed");

  assert_gesture_state (A, "a1", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (B, "b1", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (C, "c1", NULL, GTK_EVENT_SEQUENCE_CLAIMED);
  assert_gesture_state (C, "c2", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (C, "c3", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (B, "b3", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (A, "a3", NULL, GTK_EVENT_SEQUENCE_NONE);

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_claim_target (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));
  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "c1 state denied, "
                   "b1 state denied, "
                   "a1 state denied, "
                   "c2 state claimed");

  assert_gesture_state (A, "a1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (B, "b1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (C, "c1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (C, "c2", NULL, GTK_EVENT_SEQUENCE_CLAIMED);
  assert_gesture_state (C, "c3", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (B, "b3", NULL, GTK_EVENT_SEQUENCE_NONE);
  assert_gesture_state (A, "a3", NULL, GTK_EVENT_SEQUENCE_NONE);

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_claim_bubble (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "bubble c3, "
                   "bubble b3, "
                   "b1 state denied, "
                   "c3 state denied, "
                   "c2 state denied, "
                   "c1 state denied, "
                   "a1 state denied, "
                   "b3 state claimed");

  assert_gesture_state (A, "a1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (B, "b1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (C, "c1", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (C, "c2", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (C, "c3", NULL, GTK_EVENT_SEQUENCE_DENIED);
  assert_gesture_state (B, "b3", NULL, GTK_EVENT_SEQUENCE_CLAIMED);
  assert_gesture_state (A, "a3", NULL, GTK_EVENT_SEQUENCE_NONE);

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_early_claim_capture (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GtkGesture *g;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  g = add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "a1 cancelled, "
                   "b1 state claimed");

  /* Reset the string */
  g_string_erase (str, 0, str->len);

  gtk_gesture_set_state (g, GTK_EVENT_SEQUENCE_DENIED);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture c1, "
                   "b1 cancelled, "
                   "a1 cancelled, "
                   "c1 state claimed, "
                   "b1 state denied");

  point_release (&mouse_state, 1);
  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_late_claim_capture (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GtkGesture *g;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  g = add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "c1 state denied, "
                   "b1 state denied, "
                   "a1 state denied, "
                   "c2 state claimed");

  /* Reset the string */
  g_string_erase (str, 0, str->len);

  gtk_gesture_set_state (g, GTK_EVENT_SEQUENCE_CLAIMED);

  g_assert_cmpstr (str->str, ==, "");

  point_release (&mouse_state, 1);
  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

static void
test_group (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  GtkGesture *g1, *g2;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  g1 = add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_NONE);
  g2 = add_gesture (C, "c3", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_CLAIMED);
  gtk_gesture_group (g1, g2);
  add_gesture (A, "a3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b3", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c4", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c3, "
                   "c1 state denied, "
                   "b1 state denied, "
                   "a1 state denied, "
                   "c3 state claimed, "
                   "c2 state claimed, "
                   "target c2");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

G_GNUC_UNUSED static void
test_gestures_outside_grab (void)
{
  GtkWidget *A, *B, *C, *D, *E;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  D = gtk_popover_new ();
  gtk_widget_set_name (D, "D");
  gtk_widget_set_parent (D, C);

  E = gtk_image_new ();
  gtk_widget_set_name (E, "E");
  gtk_popover_set_child (GTK_POPOVER (D), E);

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (B, "b2", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a2", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "c1 state denied, "
                   "c2 state claimed");

  /* Set a grab on another window */
  g_string_erase (str, 0, str->len);
  show_popover (GTK_POPOVER (D));

  g_assert_cmpstr (str->str, ==,
                   "c1 cancelled, "
                   "c2 cancelled, "
                   "b1 cancelled, "
                   "a1 cancelled");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

G_GNUC_UNUSED static void
test_gestures_inside_grab (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_popover_new ();
  gtk_widget_set_name (B, "B");
  gtk_widget_set_parent (B, A);
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");
  gtk_popover_set_child (GTK_POPOVER (B), C);

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);

  show_toplevel (GTK_WINDOW (A));
  show_popover (GTK_POPOVER (B));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (C, "c2", GTK_PHASE_TARGET, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_gesture (B, "b2", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (A, "a2", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_NONE);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  point_update (&mouse_state, A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&mouse_state, A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1, "
                   "capture b1, "
                   "capture c1, "
                   "target c2, "
                   "c2 state claimed");

  g_string_erase (str, 0, str->len);
  g_assert_cmpstr (str->str, ==,
                   "a1 cancelled");

  /* Update with the grab under effect */
  g_string_erase (str, 0, str->len);
  point_update (&mouse_state, A, allocation.origin.x, allocation.origin.y);
  g_assert_cmpstr (str->str, ==,
                   "b1 updated, "
                   "c1 updated, "
                   "c2 updated");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

G_GNUC_UNUSED static void
test_multitouch_on_single (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_NONE);
  add_gesture (B, "b1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_CLAIMED);

  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  /* First touch down */
  point_update (&touch_state[0], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[0], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1 (1), "
                   "capture b1 (1), "
                   "a1 cancelled, "
                   "b1 state claimed (1)");

  /* Second touch down */
  g_string_erase (str, 0, str->len);
  point_update (&touch_state[1], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[1], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1 (2), "
                   "b1 state denied (2)");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

G_GNUC_UNUSED static void
test_multitouch_activation (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  add_mt_gesture (C, "c1", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  /* First touch down */
  point_update (&touch_state[0], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[0], A, 1);

  g_assert_cmpstr (str->str, ==, "");

  /* Second touch down */
  point_update (&touch_state[1], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[1], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 began, "
                   "c1 state claimed (2), "
                   "c1 state claimed (1)");

  /* First touch up */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[0], 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 ended");

  /* A third touch down triggering again action */
  g_string_erase (str, 0, str->len);
  point_update (&touch_state[2], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[2], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 began, "
                   "c1 state claimed (3)");

  /* One touch up, gesture is finished again */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[2], 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 ended");

  /* Another touch up, gesture remains inactive */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[1], 1);

  g_assert_cmpstr (str->str, ==, "");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

G_GNUC_UNUSED static void
test_multitouch_interaction (void)
{
  GtkWidget *A, *B, *C;
  GdkDisplay *display;
  GtkGesture *g;
  GString *str;
  graphene_rect_t allocation;

  A = gtk_window_new ();
  display = gtk_widget_get_display (A);
  if (gdk_display_get_default_seat (display) == NULL)
    {
      g_test_skip ("Display has no seat");
      return;
    }

  gtk_widget_set_name (A, "A");
  B = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_widget_set_name (B, "B");
  C = gtk_image_new ();
  gtk_widget_set_hexpand (C, TRUE);
  gtk_widget_set_vexpand (C, TRUE);
  gtk_widget_set_name (C, "C");

  gtk_window_set_default_size (GTK_WINDOW (A), 400400);
  gtk_window_set_child (GTK_WINDOW (A), B);
  gtk_box_append (GTK_BOX (B), C);

  show_toplevel (GTK_WINDOW (A));

  str = g_string_new ("");

  g = add_gesture (A, "a1", GTK_PHASE_CAPTURE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  add_mt_gesture (C, "c1", GTK_PHASE_BUBBLE, str, GTK_EVENT_SEQUENCE_CLAIMED);
  g_assert_true (gtk_widget_compute_bounds (B, B, &allocation));

  /* First touch down, a1 claims the sequence */
  point_update (&touch_state[0], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[0], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "capture a1 (1), "
                   "a1 state claimed (1)");

  /* Second touch down, a1 denies and c1 takes over */
  g_string_erase (str, 0, str->len);
  point_update (&touch_state[1], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[1], A, 1);

  /* Denying sequences in touch-excess situation is a responsibility of the caller */
  gtk_gesture_set_state (g, GTK_EVENT_SEQUENCE_DENIED);

  g_assert_cmpstr (str->str, ==,
                   "a1 state denied (2), "
                   "c1 began, "
                   "c1 state claimed (1), "
                   "c1 state claimed (2), "
                   "a1 state denied (1)");

  /* Move first point, only c1 should update */
  g_string_erase (str, 0, str->len);
  point_update (&touch_state[0], A, allocation.origin.x, allocation.origin.y);

  g_assert_cmpstr (str->str, ==,
                   "c1 updated");

  /* First touch up */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[0], 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 ended");

  /* A third touch down triggering again action on c1 */
  g_string_erase (str, 0, str->len);
  point_update (&touch_state[2], A,
                allocation.origin.x + allocation.size.width / 2 ,
                allocation.origin.y + allocation.size.height / 2);
  point_press (&touch_state[2], A, 1);

  g_assert_cmpstr (str->str, ==,
                   "a1 state denied (3), "
                   "c1 began, "
                   "c1 state claimed (3)");

  /* One touch up, gesture is finished again */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[2], 1);

  g_assert_cmpstr (str->str, ==,
                   "c1 ended");

  /* Another touch up, gesture remains inactive */
  g_string_erase (str, 0, str->len);
  point_release (&touch_state[1], 1);

  g_assert_cmpstr (str->str, ==, "");

  gtk_window_destroy (GTK_WINDOW (A));

  g_string_free (str, TRUE);
}

int
main (int argc, char *argv[])
{
  gtk_test_init (&argc, &argv);

  g_test_add_func ("/gestures/propagation/phases", test_phases);
  g_test_add_func ("/gestures/propagation/mixed", test_mixed);
  g_test_add_func ("/gestures/propagation/early-exit", test_early_exit);
  g_test_add_func ("/gestures/claim/capture", test_claim_capture);
  g_test_add_func ("/gestures/claim/target", test_claim_target);
  g_test_add_func ("/gestures/claim/bubble", test_claim_bubble);
  g_test_add_func ("/gestures/claim/early-capture", test_early_claim_capture);
  g_test_add_func ("/gestures/claim/late-capture", test_late_claim_capture);
  g_test_add_func ("/gestures/group", test_group);
#if 0
  g_test_add_func ("/gestures/grabs/gestures-outside-grab", test_gestures_outside_grab);
  g_test_add_func ("/gestures/grabs/gestures-inside-grab", test_gestures_inside_grab);
  g_test_add_func ("/gestures/multitouch/gesture-single", test_multitouch_on_single);
  g_test_add_func ("/gestures/multitouch/multitouch-activation", test_multitouch_activation);
  g_test_add_func ("/gestures/multitouch/interaction", test_multitouch_interaction);
#endif

  return g_test_run ();
}

Messung V0.5 in Prozent
C=100 H=91 G=95

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet am  2026-07-02) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik