[gtk+/xi2-playground: 9/15] Add GtkDeviceGroup and ::multidevice-event



commit bb77cae01d8a711fa10df0e2ebbfe5da390ce3db
Author: Carlos Garnacho <carlosg gnome org>
Date:   Mon Jun 7 15:41:10 2010 +0200

    Add GtkDeviceGroup and ::multidevice-event
    
    The former allows to create several per-widget device groups, so the latter
    is emitted on these containing motion events for all devices in the reported
    group.

 gtk/Makefile.am |    2 +
 gtk/gtk.symbols |   12 ++
 gtk/gtkwidget.c |  380 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkwidget.h |   44 +++++++
 4 files changed, 438 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index e10be5f..30bd83a 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -204,6 +204,7 @@ gtk_public_h_sources =          \
 	gtkcomboboxentry.h	\
 	gtkcontainer.h		\
 	gtkdebug.h              \
+	gtkdevicegroup.h	\
 	gtkdialog.h		\
 	gtkdnd.h		\
 	gtkdrawingarea.h	\
@@ -462,6 +463,7 @@ gtk_base_c_sources =            \
 	gtkcombobox.c		\
 	gtkcomboboxentry.c	\
 	gtkcontainer.c		\
+	gtkdevicegroup.c	\
 	gtkdialog.c		\
 	gtkdrawingarea.c	\
 	gtkeditable.c           \
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 6439b90..11cc365 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -4503,10 +4503,22 @@ gtk_widget_set_mapped
 gtk_widget_get_mapped
 gtk_widget_get_support_multidevice
 gtk_widget_set_support_multidevice
+gtk_widget_create_device_group
+gtk_widget_remove_device_group
+gtk_widget_get_group_for_device
 gtk_widget_device_is_shadowed
 #endif
 #endif
 
+#if IN_HEADER(__GTK_DEVICE_GROUP_H__)
+#if IN_FILE(__GTK_DEVICE_GROUP_C__)
+gtk_device_group_get_type G_GNUC_CONST
+gtk_device_group_add_device
+gtk_device_group_remove_device
+gtk_device_group_get_devices
+#endif
+#endif
+
 #if IN_HEADER(__GTK_WINDOW_H__)
 #if IN_FILE(__GTK_WINDOW_C__)
 gtk_window_activate_default
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 63b7cb0..6eea6ff 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -198,6 +198,7 @@ enum {
   KEYNAV_FAILED,
   DRAG_FAILED,
   DAMAGE_EVENT,
+  MULTIDEVICE_EVENT,
   LAST_SIGNAL
 };
 
@@ -229,6 +230,7 @@ enum {
 };
 
 typedef	struct	_GtkStateData	 GtkStateData;
+typedef struct  _GtkMultiDeviceData GtkMultiDeviceData;
 
 struct _GtkStateData
 {
@@ -238,6 +240,12 @@ struct _GtkStateData
   guint		use_forall : 1;
 };
 
+struct _GtkMultiDeviceData
+{
+  GList *groups;
+  GHashTable *by_dev;
+};
+
 /* --- prototypes --- */
 static void	gtk_widget_class_init		(GtkWidgetClass     *klass);
 static void	gtk_widget_base_class_finalize	(GtkWidgetClass     *klass);
@@ -390,6 +398,8 @@ static GQuark		quark_mnemonic_labels = 0;
 static GQuark		quark_tooltip_markup = 0;
 static GQuark		quark_has_tooltip = 0;
 static GQuark		quark_tooltip_window = 0;
+static GQuark           quark_multidevice_events = 0;
+static GQuark           quark_multidevice_data = 0;
 GParamSpecPool         *_gtk_widget_child_property_pool = NULL;
 GObjectNotifyContext   *_gtk_widget_child_property_notify_context = NULL;
 
@@ -487,6 +497,8 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   quark_tooltip_markup = g_quark_from_static_string ("gtk-tooltip-markup");
   quark_has_tooltip = g_quark_from_static_string ("gtk-has-tooltip");
   quark_tooltip_window = g_quark_from_static_string ("gtk-tooltip-window");
+  quark_multidevice_events = g_quark_from_static_string ("gtk-multidevice-events");
+  quark_multidevice_data = g_quark_from_static_string ("gtk-multidevice-data");
 
   style_property_spec_pool = g_param_spec_pool_new (FALSE);
   _gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -2399,6 +2411,29 @@ gtk_widget_class_init (GtkWidgetClass *klass)
 		  _gtk_marshal_BOOLEAN__UINT,
                   G_TYPE_BOOLEAN, 1, G_TYPE_UINT);
 
+  /**
+   * GtkWidget::multidevice-event:
+   * @widget: the object which received the signal
+   * @device_group: the #GtkDeviceGroup that was updated by a
+   *                device event
+   * @event: a #GtkMultiDeviceEvent containing event information
+   *         for all devices in @device_group.
+   *
+   * This signal is emitted right after an input device that is
+   * contained in @device_group emits one event, or whenever a
+   * #GdkDevice is added or removed from @device_group.
+   *
+   * Since: 3.0
+   */
+  widget_signals[MULTIDEVICE_EVENT] =
+    g_signal_new (I_("multidevice-event"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  _gtk_marshal_VOID__OBJECT_POINTER,
+                  G_TYPE_NONE, 2,
+                  GTK_TYPE_DEVICE_GROUP, G_TYPE_POINTER);
+
   binding_set = gtk_binding_set_by_class (klass);
   gtk_binding_entry_add_signal (binding_set, GDK_F10, GDK_SHIFT_MASK,
                                 "popup-menu", 0);
@@ -4755,6 +4790,85 @@ event_window_is_still_viewable (GdkEvent *event)
     }
 }
 
+static void
+compose_multidevice_event (GtkWidget               *widget,
+                           GdkDevice               *device,
+                           GdkEventMotion          *new_event)
+{
+  GHashTable *multidevice_events;
+  GtkMultiDeviceEventType type;
+  GtkDeviceGroup *group;
+  GtkMultiDeviceData *data;
+  GtkMultiDeviceEvent event;
+  GdkEvent *updated_event;
+  GList *devices;
+  gint i = 0;
+
+  data = g_object_get_qdata ((GObject *) widget,
+                             quark_multidevice_data);
+  if (!data)
+    return;
+
+  group = g_hash_table_lookup (data->by_dev, device);
+
+  if (!group)
+    return;
+
+  multidevice_events = g_object_get_qdata ((GObject *) widget,
+                                           quark_multidevice_events);
+
+  if (G_UNLIKELY (!multidevice_events))
+    {
+      multidevice_events = g_hash_table_new_full (g_direct_hash,
+                                                   g_direct_equal,
+                                                   (GDestroyNotify) g_object_unref,
+                                                   (GDestroyNotify) gdk_event_free);
+      g_object_set_qdata_full ((GObject *) widget,
+                               quark_multidevice_events,
+			       multidevice_events,
+                               (GDestroyNotify) g_hash_table_destroy);
+    }
+
+  if (!new_event)
+    {
+      g_hash_table_remove (multidevice_events, device);
+      type = GTK_EVENT_DEVICE_REMOVED;
+      updated_event = NULL;
+    }
+  else
+    {
+      if (g_hash_table_lookup (multidevice_events, device) == NULL)
+        type = GTK_EVENT_DEVICE_ADDED;
+      else
+        type = GTK_EVENT_DEVICE_UPDATED;
+
+      updated_event = gdk_event_copy ((GdkEvent *) new_event);
+
+      g_hash_table_insert (multidevice_events,
+                           g_object_ref (device),
+                           updated_event);
+    }
+
+  devices = gtk_device_group_get_devices (group);
+
+  /* Compose event */
+  event.type = type;
+  event.n_events = g_list_length (devices);
+  event.events = g_new0 (GdkEventMotion *, event.n_events);
+  event.updated_event = (GdkEventMotion *) updated_event;
+  event.updated_device = device;
+
+  while (devices)
+    {
+      event.events[i] = g_hash_table_lookup (multidevice_events, devices->data);
+      devices = devices->next;
+      i++;
+    }
+
+  g_signal_emit (widget, widget_signals[MULTIDEVICE_EVENT], 0, group, &event);
+  g_free (event.events);
+}
+
 static gint
 gtk_widget_event_internal (GtkWidget *widget,
 			   GdkEvent  *event)
@@ -4877,6 +4991,15 @@ gtk_widget_event_internal (GtkWidget *widget,
 	}
       if (signal_num != -1)
 	g_signal_emit (widget, widget_signals[signal_num], 0, event, &return_val);
+
+      if (event->type == GDK_MOTION_NOTIFY &&
+          (GTK_WIDGET_FLAGS (widget) & GTK_MULTIDEVICE) != 0)
+        {
+          GdkEventMotion *event_motion;
+
+          event_motion = (GdkEventMotion *) event;
+          compose_multidevice_event (widget, event_motion->device, event_motion);
+        }
     }
   if (WIDGET_REALIZED_FOR_EVENT (widget, event))
     g_signal_emit (widget, widget_signals[EVENT_AFTER], 0, event);
@@ -11507,6 +11630,263 @@ gtk_widget_set_support_multidevice (GtkWidget *widget,
     gdk_window_set_support_multidevice (widget->window, support_multidevice);
 }
 
+static GdkEventMotion *
+convert_event_to_motion (GdkEvent *event)
+{
+  GdkEventMotion *new_event;
+
+  new_event = (GdkEventMotion *) gdk_event_new (GDK_MOTION_NOTIFY);
+
+  switch (event->type)
+    {
+    case GDK_BUTTON_PRESS:
+    case GDK_2BUTTON_PRESS:
+    case GDK_3BUTTON_PRESS:
+    case GDK_BUTTON_RELEASE:
+      new_event->window = g_object_ref (event->button.window);
+      new_event->send_event = TRUE;
+      new_event->time = event->button.time;
+      new_event->x = event->button.x;
+      new_event->y = event->button.y;
+
+      if (event->button.axes)
+	new_event->axes = g_memdup (event->button.axes,
+                                    sizeof (gdouble) * event->button.device->num_axes);
+
+      new_event->state = 0; /* FIXME */
+      new_event->is_hint = FALSE;
+      new_event->device = event->button.device;
+      new_event->x_root = event->button.x_root;
+      new_event->y_root = event->button.y_root;
+      break;
+    case GDK_ENTER_NOTIFY:
+    case GDK_LEAVE_NOTIFY:
+      new_event->window = g_object_ref (event->crossing.window);
+      new_event->send_event = TRUE;
+      new_event->time = event->crossing.time;
+      new_event->x = event->crossing.x;
+      new_event->y = event->crossing.y;
+      new_event->axes = NULL; /* FIXME: not ideal for non-mice */
+      new_event->state = 0; /* FIXME */
+      new_event->is_hint = FALSE;
+      new_event->x_root = event->crossing.x_root;
+      new_event->y_root = event->crossing.y_root;
+      gdk_event_set_device ((GdkEvent *) new_event, gdk_event_get_device ((GdkEvent *) event));
+      break;
+    default:
+      g_warning ("Event with type %d can not be transformed to GdkEventMotion", event->type);
+      gdk_event_free ((GdkEvent *) new_event);
+      new_event = NULL;
+    }
+
+  return new_event;
+}
+
+static void
+device_group_device_added (GtkDeviceGroup *group,
+                           GdkDevice      *device,
+                           GtkWidget      *widget)
+{
+  GtkMultiDeviceData *data;
+  GtkDeviceGroup *old_group;
+  GdkEventMotion *new_event = NULL;
+  GtkWidget *event_widget;
+  GdkEvent *event;
+
+  data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
+
+  if (G_UNLIKELY (!data))
+    return;
+
+  /* Remove device from old group, if any */
+  old_group = g_hash_table_lookup (data->by_dev, device);
+
+  if (old_group)
+    gtk_device_group_remove_device (old_group, device);
+
+  g_hash_table_insert (data->by_dev,
+                       g_object_ref (device),
+                       g_object_ref (group));
+
+  event = gtk_get_current_event ();
+
+  if (!event)
+    return;
+
+  if (event->type == GDK_MOTION_NOTIFY)
+    new_event = (GdkEventMotion *) event;
+  else
+    {
+      event_widget = gtk_get_event_widget (event);
+
+      if (widget == event_widget)
+        new_event = convert_event_to_motion (event);
+
+      gdk_event_free (event);
+    }
+
+  if (new_event)
+    {
+      gtk_widget_event_internal (widget, (GdkEvent *) new_event);
+      gdk_event_free ((GdkEvent *) new_event);
+    }
+}
+
+static void
+device_group_device_removed (GtkDeviceGroup *group,
+                             GdkDevice      *device,
+                             GtkWidget      *widget)
+{
+  GtkMultiDeviceData *data;
+
+  data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
+
+  g_assert (data != NULL);
+
+  compose_multidevice_event (widget, device, NULL);
+  g_hash_table_remove (data->by_dev, device);
+}
+
+static void
+free_multidevice_data (GtkMultiDeviceData *data)
+{
+  g_list_foreach (data->groups, (GFunc) g_object_unref, NULL);
+  g_list_free (data->groups);
+
+  g_hash_table_destroy (data->by_dev);
+
+  g_slice_free (GtkMultiDeviceData, data);
+}
+
+/**
+ * gtk_widget_create_device_group:
+ * @widget: a #GtkWidget
+ *
+ * Creates a new #GtkDeviceGroup for @widget. devices can be added
+ * later through gtk_device_group_add_device(). Note that
+ * #GdkDevice<!-- -->s can only pertain to one #GtkDeviceGroup for a
+ * given #GtkWidget, so adding it to a new group will remove it from
+ * its previous one.
+ *
+ * Returns: a newly created #GtkDeviceGroup. This object is owned by
+ *          @widget and must be destroyed through
+ *          gtk_widget_remove_device_group().
+ *
+ * Since: 3.0
+ **/
+GtkDeviceGroup *
+gtk_widget_create_device_group (GtkWidget *widget)
+{
+  GtkMultiDeviceData *data;
+  GtkDeviceGroup *group;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+  group = g_object_new (GTK_TYPE_DEVICE_GROUP, NULL);
+
+  g_signal_connect (group, "device-added",
+                    G_CALLBACK (device_group_device_added), widget);
+  g_signal_connect (group, "device-removed",
+                    G_CALLBACK (device_group_device_removed), widget);
+
+  data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
+
+  if (G_UNLIKELY (!data))
+    {
+      data = g_slice_new0 (GtkMultiDeviceData);
+      data->by_dev = g_hash_table_new_full (g_direct_hash,
+                                            g_direct_equal,
+                                            (GDestroyNotify) g_object_unref,
+                                            (GDestroyNotify) g_object_unref);
+
+      g_object_set_qdata_full (G_OBJECT (widget),
+                               quark_multidevice_data,
+                               data,
+                               (GDestroyNotify) free_multidevice_data);
+    }
+
+  data->groups = g_list_prepend (data->groups, group);
+
+  return group;
+}
+
+/**
+ * gtk_widget_remove_device_group:
+ * @widget: a #GtkWidget
+ * @group: a #GtkDeviceGroup
+ *
+ * If @group pertains to @widget, @group will be destroyed so no further
+ * #GtkWidget::multidevice-event<!-- -->s are emitted for it.
+ *
+ * Since: 3.0
+ **/
+void
+gtk_widget_remove_device_group (GtkWidget      *widget,
+                                GtkDeviceGroup *group)
+{
+  GtkMultiDeviceData *data;
+  GList *devices, *g;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
+
+  data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
+
+  if (G_UNLIKELY (!data))
+    return;
+
+  g = g_list_find (data->groups, group);
+
+  if (G_UNLIKELY (!g))
+    return;
+
+  devices = gtk_device_group_get_devices (group);
+
+  /* Free per-device data */
+  while (devices)
+    {
+      g_hash_table_remove (data->by_dev, devices->data);
+      devices = devices->next;
+    }
+
+  /* Free group */
+  data->groups = g_list_remove_link (data->groups, g);
+  g_object_unref (g->data);
+  g_list_free_1 (g);
+}
+
+/**
+ * gtk_widget_get_group_for_device:
+ * @widget: a #GtkWidget
+ * @device: a #GdkDevice
+ *
+ * Returns the #GtkDeviceGroup containing the #GdkDevice, or %NULL if
+ * there is none.
+ *
+ * Returns: a #GtkDeviceGroup, or %NULL.
+ *
+ * Since: 3.0
+ **/
+GtkDeviceGroup *
+gtk_widget_get_group_for_device (GtkWidget *widget,
+                                 GdkDevice *device)
+{
+  GtkMultiDeviceData *data;
+  GtkDeviceGroup *group = NULL;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+  g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
+
+  data = g_object_get_qdata (G_OBJECT (widget), quark_multidevice_data);
+
+  if (!data)
+    return NULL;
+
+  group = g_hash_table_lookup (data->by_dev, device);
+
+  return group;
+}
+
 static void
 _gtk_widget_set_has_focus (GtkWidget *widget,
                            gboolean   has_focus)
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index 92f6fe1..3eaf36e 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -37,6 +37,7 @@
 #include <gtk/gtkadjustment.h>
 #include <gtk/gtkstyle.h>
 #include <gtk/gtksettings.h>
+#include <gtk/gtkdevicegroup.h>
 #include <atk/atk.h>
 
 G_BEGIN_DECLS
@@ -202,6 +203,7 @@ typedef struct _GtkWidgetShapeInfo GtkWidgetShapeInfo;
 typedef struct _GtkClipboard	   GtkClipboard;
 typedef struct _GtkTooltip         GtkTooltip;
 typedef struct _GtkWindow          GtkWindow;
+typedef struct _GtkMultiDeviceEvent GtkMultiDeviceEvent;
 
 
 /**
@@ -533,6 +535,43 @@ struct _GtkWidgetClass
   void (*_gtk_reserved7) (void);
 };
 
+/**
+ * GtkMultiDeviceEventType:
+ * @GTK_EVENT_DEVICE_ADDED: A device was added to the device group.
+ * @GTK_EVENT_DEVICE_REMOVED: A device was removed from the device group.
+ * @GTK_EVENT_DEVICE_UPDATED: A device in the device group has updated its
+ *                            state.
+ *
+ * Provides a hint about the change that initiated the
+ * #GtkWidget::multidevice-event.
+ */
+typedef enum
+{
+  GTK_EVENT_DEVICE_ADDED,
+  GTK_EVENT_DEVICE_REMOVED,
+  GTK_EVENT_DEVICE_UPDATED
+} GtkMultiDeviceEventType;
+
+/**
+ * GtkMultiDeviceEvent:
+ * @type: the event type.
+ * @n_events: number of device events contained in this #GtkMultiDeviceEvent
+ * @events: an array of #GdkEventMotion structs.
+ * @updated_event: latest updated event, or %NULL if @type is %GDK_EVENT_DEVICE_REMOVED.
+ * @updated_device: device that triggered the event.
+ *
+ * The #GtkMultiDeviceEvent struct contains information about latest state of
+ * multiple device pointers. These devices are contained in a #GtkDeviceGroup.
+ */
+struct _GtkMultiDeviceEvent
+{
+  GtkMultiDeviceEventType type;
+  guint n_events;
+  GdkEventMotion **events;
+  GdkEventMotion *updated_event;
+  GdkDevice *updated_device;
+};
+
 struct _GtkWidgetAuxInfo
 {
   gint x;
@@ -766,6 +805,11 @@ GdkPixmap *   gtk_widget_get_snapshot    (GtkWidget    *widget,
 gboolean         gtk_widget_get_support_multidevice (GtkWidget      *widget);
 void             gtk_widget_set_support_multidevice (GtkWidget      *widget,
                                                      gboolean        support_multidevice);
+GtkDeviceGroup * gtk_widget_get_group_for_device    (GtkWidget      *widget,
+                                                     GdkDevice      *device);
+GtkDeviceGroup * gtk_widget_create_device_group     (GtkWidget      *widget);
+void             gtk_widget_remove_device_group     (GtkWidget      *widget,
+                                                     GtkDeviceGroup *group);
 
 /* Accessibility support */
 AtkObject*       gtk_widget_get_accessible               (GtkWidget          *widget);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]