[gtk+/xi2-playground: 6/12] Add GtkDeviceGroup and ::multidevice-event



commit 7b369726084cc56e36c20b1d57e9ef9492996262
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/gtkdevicegroup.c |  215 ++++++++++++++++++++++++++++
 gtk/gtkdevicegroup.h |   71 ++++++++++
 gtk/gtkwidget.c      |  380 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkwidget.h      |   44 ++++++
 6 files changed, 724 insertions(+), 0 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 3e791dc..0c1ccd5 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 eba4e8e..112b64f 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -4486,10 +4486,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/gtkdevicegroup.c b/gtk/gtkdevicegroup.c
new file mode 100644
index 0000000..ef9b0ee
--- /dev/null
+++ b/gtk/gtkdevicegroup.c
@@ -0,0 +1,215 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "gtkintl.h"
+#include "gtkdevicegroup.h"
+#include "gtkalias.h"
+
+/**
+ * SECTION:gtkdevicegroup
+ * @Short_description: Group of input devices for multidevice events.
+ * @Title: Device groups
+ * @See_also: #GtkMultiDeviceEvent
+ *
+ * #GtkDeviceGroup defines a group of devices, they are created through
+ * gtk_widget_create_device_group() and destroyed through
+ * gtk_widget_remove_device_group(). Device groups are used by its
+ * corresponding #GtkWidget in order to issue #GtkMultiDeviceEvent<!-- -->s
+ * whenever any of the contained devices emits a #GDK_MOTION_NOTIFY
+ * event, or any device enters or leaves the group.
+ */
+
+typedef struct GtkDeviceGroupPrivate GtkDeviceGroupPrivate;
+
+struct GtkDeviceGroupPrivate
+{
+  GList *devices;
+};
+
+#define GTK_DEVICE_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupPrivate))
+
+static void gtk_device_group_class_init (GtkDeviceGroupClass *klass);
+static void gtk_device_group_init       (GtkDeviceGroup      *group);
+
+static void gtk_device_group_finalize   (GObject             *object);
+
+enum {
+  DEVICE_ADDED,
+  DEVICE_REMOVED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GtkDeviceGroup, gtk_device_group, G_TYPE_OBJECT)
+
+
+static void
+gtk_device_group_class_init (GtkDeviceGroupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_device_group_finalize;
+
+  /**
+   * GtkDeviceGroup::device-added:
+   * @device_group: the object that received the signal
+   * @device: the device that was just added
+   *
+   * This signal is emitted right after a #GdkDevice is added
+   * to @device_group.
+   */
+  signals[DEVICE_ADDED] =
+    g_signal_new (I_("device-added"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkDeviceGroupClass, device_added),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
+  /**
+   * GtkDeviceGroup::device-removed:
+   * @device_group: the object that received the signal
+   * @device: the device that was just removed
+   *
+   * This signal is emitted right after a #GdkDevice is removed
+   * from @device_group.
+   */
+  signals[DEVICE_REMOVED] =
+    g_signal_new (I_("device-removed"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkDeviceGroupClass, device_removed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1, GDK_TYPE_DEVICE);
+
+  g_type_class_add_private (object_class, sizeof (GtkDeviceGroupPrivate));
+}
+
+static void
+gtk_device_group_init (GtkDeviceGroup *group)
+{
+}
+
+static void
+gtk_device_group_finalize (GObject *object)
+{
+  GtkDeviceGroupPrivate *priv;
+
+  priv = GTK_DEVICE_GROUP_GET_PRIVATE (object);
+
+  g_list_foreach (priv->devices, (GFunc) g_object_unref, NULL);
+  g_list_free (priv->devices);
+
+  G_OBJECT_CLASS (gtk_device_group_parent_class)->finalize (object);
+}
+
+/**
+ * gtk_device_group_add_device:
+ * @group: a #GtkDeviceGroup
+ * @device: a #GdkDevice
+ *
+ * Adds @device to @group, so events coming from this device will
+ * trigger #GtkWidget::multidevice-event<!-- -->s for @group. Adding
+ * devices with source %GDK_SOURCE_KEYBOARD is not allowed.
+ *
+ * Since: 3.0
+ **/
+void
+gtk_device_group_add_device (GtkDeviceGroup *group,
+                             GdkDevice      *device)
+{
+  GtkDeviceGroupPrivate *priv;
+
+  g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
+  g_return_if_fail (GDK_IS_DEVICE (device));
+  g_return_if_fail (device->source != GDK_SOURCE_KEYBOARD);
+
+  priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
+
+  if (g_list_find (priv->devices, device))
+    return;
+
+  priv->devices = g_list_prepend (priv->devices,
+                                  g_object_ref (device));
+
+  g_signal_emit (group, signals[DEVICE_ADDED], 0, device);
+}
+
+/**
+ * gtk_device_group_remove_device:
+ * @group: a #GtkDeviceGroup
+ * @device: a #GdkDevice
+ *
+ * Removes @device from @group, if it was there.
+ *
+ * Since: 3.0
+ **/
+void
+gtk_device_group_remove_device (GtkDeviceGroup *group,
+                                GdkDevice      *device)
+{
+  GtkDeviceGroupPrivate *priv;
+  GList *dev;
+
+  g_return_if_fail (GTK_IS_DEVICE_GROUP (group));
+  g_return_if_fail (GDK_IS_DEVICE (device));
+
+  priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
+
+  dev = g_list_find (priv->devices, device);
+
+  if (!dev)
+    return;
+
+  priv->devices = g_list_remove_link (priv->devices, dev);
+
+  g_signal_emit (group, signals[DEVICE_REMOVED], 0, device);
+
+  g_object_unref (dev->data);
+  g_list_free_1 (dev);
+}
+
+/**
+ * gtk_device_group_get_devices:
+ * @group: a #GtkDeviceGroup
+ *
+ * Returns a #GList of #GdkDevices with the devices contained in @group.
+ *
+ * Returns: a list of #GdkDevices. This list and its elements are owned
+ *          by group, and must not be freed or unref'ed.
+ *
+ * Since: 3.0
+ **/
+GList *
+gtk_device_group_get_devices (GtkDeviceGroup *group)
+{
+  GtkDeviceGroupPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_DEVICE_GROUP (group), NULL);
+
+  priv = GTK_DEVICE_GROUP_GET_PRIVATE (group);
+
+  return priv->devices;
+}
+
+#define __GTK_DEVICE_GROUP_C__
+#include "gtkaliasdef.c"
diff --git a/gtk/gtkdevicegroup.h b/gtk/gtkdevicegroup.h
new file mode 100644
index 0000000..c09be77
--- /dev/null
+++ b/gtk/gtkdevicegroup.h
@@ -0,0 +1,71 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if defined(GTK_DISABLE_SINGLE_INCLUDES) && !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_DEVICE_GROUP_H__
+#define __GTK_DEVICE_GROUP_H__
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct GtkDeviceGroup GtkPointerGroup;
+
+#define GTK_TYPE_DEVICE_GROUP         (gtk_device_group_get_type ())
+#define GTK_DEVICE_GROUP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroup))
+#define GTK_DEVICE_GROUP_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST    ((c), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupClass))
+#define GTK_IS_DEVICE_GROUP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_DEVICE_GROUP))
+#define GTK_IS_DEVICE_GROUP_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE    ((c), GTK_TYPE_DEVICE_GROUP))
+#define GTK_DEVICE_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS  ((o), GTK_TYPE_DEVICE_GROUP, GtkDeviceGroupClass))
+
+typedef struct GtkDeviceGroup GtkDeviceGroup;
+typedef struct GtkDeviceGroupClass GtkDeviceGroupClass;
+
+struct GtkDeviceGroup
+{
+  GObject parent_instance;
+};
+
+struct GtkDeviceGroupClass
+{
+  GObjectClass parent_class;
+
+  void (* device_added)   (GtkDeviceGroup *group,
+                           GdkDevice      *device);
+  void (* device_removed) (GtkDeviceGroup *group,
+                           GdkDevice      *device);
+};
+
+GType   gtk_device_group_get_type       (void) G_GNUC_CONST;
+
+void    gtk_device_group_add_device     (GtkDeviceGroup  *group,
+                                         GdkDevice       *device);
+
+void    gtk_device_group_remove_device  (GtkDeviceGroup  *group,
+                                         GdkDevice       *device);
+
+GList * gtk_device_group_get_devices    (GtkDeviceGroup  *group);
+
+
+G_END_DECLS
+
+#endif /* __GTK_DEVICE_GROUP_H__ */
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 7e32528..8155ffe 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -200,6 +200,7 @@ enum {
   KEYNAV_FAILED,
   DRAG_FAILED,
   DAMAGE_EVENT,
+  MULTIDEVICE_EVENT,
   LAST_SIGNAL
 };
 
@@ -231,6 +232,7 @@ enum {
 };
 
 typedef	struct	_GtkStateData	 GtkStateData;
+typedef struct  _GtkMultiDeviceData GtkMultiDeviceData;
 
 struct _GtkStateData
 {
@@ -240,6 +242,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);
@@ -392,6 +400,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;
 
@@ -489,6 +499,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);
@@ -2420,6 +2432,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);
@@ -4776,6 +4811,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)
@@ -4898,6 +5012,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);
@@ -11528,6 +11651,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]