[gtk+/xi2-playground: 10/15] Add test applications for multidevice events.



commit dcb1747ba9114ddf8acaf0e7597faf8df2516bf0
Author: Carlos Garnacho <carlosg gnome org>
Date:   Mon Jun 7 15:44:00 2010 +0200

    Add test applications for multidevice events.

 configure.ac                             |    1 +
 tests/Makefile.am                        |    2 +
 tests/multidevice/Makefile.am            |   47 +++
 tests/multidevice/testangle.c            |  197 ++++++++++++
 tests/multidevice/testcoordinates.c      |   45 +++
 tests/multidevice/testcoordinatewidget.c |  251 +++++++++++++++
 tests/multidevice/testcoordinatewidget.h |   57 ++++
 tests/multidevice/testphotoalbum.c       |  111 +++++++
 tests/multidevice/testphotoalbumwidget.c |  508 ++++++++++++++++++++++++++++++
 tests/multidevice/testphotoalbumwidget.h |   60 ++++
 10 files changed, 1279 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 8827cb0..320f46a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2116,6 +2116,7 @@ demos/Makefile
 demos/gtk-demo/Makefile
 demos/gtk-demo/geninclude.pl
 tests/Makefile
+tests/multidevice/Makefile
 docs/Makefile
 docs/reference/Makefile
 docs/reference/gdk-pixbuf/Makefile
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a7c9705..53046d2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,6 +1,8 @@
 ## Makefile.am for gtk+/tests
 include $(top_srcdir)/Makefile.decl
 
+SUBDIRS = multidevice
+
 INCLUDES =				\
 	-I$(top_srcdir)			\
 	-I$(top_builddir)/gdk		\
diff --git a/tests/multidevice/Makefile.am b/tests/multidevice/Makefile.am
new file mode 100644
index 0000000..a26d276
--- /dev/null
+++ b/tests/multidevice/Makefile.am
@@ -0,0 +1,47 @@
+## Makefile.am for gtk+/tests/mpx
+include $(top_srcdir)/Makefile.decl
+
+INCLUDES =				\
+	-I$(top_srcdir)			\
+	-I$(top_builddir)/gdk		\
+	-I$(top_srcdir)/gdk		\
+	-DGDK_DISABLE_DEPRECATED	\
+	-DGTK_DISABLE_DEPRECATED	\
+	$(GTK_DEBUG_FLAGS)		\
+	$(GTK_DEP_CFLAGS)
+
+DEPS =									\
+	$(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GTK_API_VERSION).la	\
+	$(top_builddir)/gdk/$(gdktargetlib)				\
+	$(top_builddir)/gtk/$(gtktargetlib)
+
+LDADDS =								\
+	$(top_builddir)/gdk-pixbuf/libgdk_pixbuf-$(GTK_API_VERSION).la	\
+	$(top_builddir)/gdk/$(gdktargetlib)				\
+	$(top_builddir)/gtk/$(gtktargetlib)
+
+noinst_PROGRAMS = 			\
+	testangle			\
+	testcoordinates			\
+	testphotoalbum
+
+testangle_DEPENDENCIES = $(TEST_DEPS)
+testcoordinates_DEPENDENCIES = $(TEST_DEPS)
+testphotoalbum_DEPENDENCIES = $(TEST_DEPS)
+
+testangle_LDADD = $(LDADDS)
+testcoordinates_LDADD = $(LDADDS)
+testphotoalbum_LDADD = $(LDADDS)
+
+testangle_SOURCES =		\
+	testangle.c
+
+testcoordinates_SOURCES = 	\
+	testcoordinatewidget.c	\
+	testcoordinatewidget.h	\
+	testcoordinates.c
+
+testphotoalbum_SOURCES =	\
+	testphotoalbumwidget.c	\
+	testphotoalbumwidget.h	\
+	testphotoalbum.c
diff --git a/tests/multidevice/testangle.c b/tests/multidevice/testangle.c
new file mode 100644
index 0000000..c87ec46
--- /dev/null
+++ b/tests/multidevice/testangle.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <gtk/gtk.h>
+
+typedef struct DeviceData DeviceData;
+typedef struct Data Data;
+
+struct Data
+{
+  GtkDeviceGroup *group;
+
+  gdouble x;
+  gdouble y;
+  gdouble angle;
+  gdouble distance;
+};
+
+static gboolean
+button_press_cb (GtkWidget *widget,
+                 GdkEvent  *event,
+                 gpointer   user_data)
+{
+  Data *data;
+  GList *devices;
+
+  data = (Data *) user_data;
+  devices = gtk_device_group_get_devices (data->group);
+
+  if (g_list_length (devices) < 2)
+    gtk_device_group_add_device (data->group, gdk_event_get_device (event));
+
+  return FALSE;
+}
+
+static gboolean
+button_release_cb (GtkWidget *widget,
+                   GdkEvent  *event,
+                   gpointer   user_data)
+{
+  Data *data;
+
+  data = (Data *) user_data;
+
+  gtk_device_group_remove_device (data->group, gdk_event_get_device (event));
+
+  return FALSE;
+}
+
+static void
+multidevice_cb (GtkWidget           *widget,
+                GtkDeviceGroup      *group,
+                GtkMultiDeviceEvent *event,
+                gpointer             user_data)
+{
+  Data *data;
+
+  data = user_data;
+
+  if (event->n_events > 0)
+    {
+      data->x = event->events[0]->x;
+      data->y = event->events[0]->y;
+    }
+
+  if (event->n_events > 1)
+    {
+      gdk_events_get_distance ((GdkEvent *) event->events[0],
+                               (GdkEvent *) event->events[1],
+                               &data->distance);
+      gdk_events_get_angle ((GdkEvent *) event->events[0],
+                            (GdkEvent *) event->events[1],
+                            &data->angle);
+    }
+  else
+    {
+      data->distance = 0;
+      data->angle = 0;
+    }
+
+  gtk_widget_queue_draw (widget);
+}
+
+static gboolean
+expose_cb (GtkWidget      *widget,
+           GdkEventExpose *event,
+           gpointer        user_data)
+{
+  Data *data;
+  cairo_t *cr;
+  GList *devices;
+
+  data = (Data *) user_data;
+
+  devices = gtk_device_group_get_devices (data->group);
+
+  if (!devices)
+    return TRUE;
+
+  cr = gdk_cairo_create (widget->window);
+  cairo_translate (cr, data->x, data->y);
+
+  cairo_save (cr);
+
+  cairo_set_source_rgb (cr, 0., 0., 0.);
+  cairo_move_to (cr, 0, 0);
+  cairo_rel_line_to (cr, 1000, 0);
+  cairo_stroke (cr);
+
+  cairo_restore (cr);
+
+  if (data->angle > 0)
+    {
+      cairo_save (cr);
+
+      cairo_set_source_rgb (cr, 0, 0, 1);
+      cairo_move_to (cr, 0, 0);
+
+      cairo_arc (cr,
+                 0.,
+                 0.,
+                 MAX (10., data->distance),
+                 0.,
+                 data->angle);
+
+      cairo_close_path (cr);
+
+      cairo_fill (cr);
+
+      cairo_restore (cr);
+
+      cairo_set_source_rgb (cr, 1., 0., 0.);
+      cairo_rotate (cr, data->angle);
+      cairo_move_to (cr, 0, 0);
+      cairo_rel_line_to (cr, data->distance, 0);
+      cairo_stroke (cr);
+    }
+
+  cairo_destroy (cr);
+
+  return TRUE;
+}
+
+int
+main (int argc, gchar *argv[])
+{
+  GtkWidget *window;
+  Data data = { 0 };
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_app_paintable (window, TRUE);
+
+  data.group = gtk_widget_create_device_group (window);
+
+  gtk_widget_add_events (window,
+                         (GDK_POINTER_MOTION_MASK |
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                          GDK_BUTTON_MOTION_MASK));
+
+  gtk_widget_set_support_multidevice (window, TRUE);
+
+  g_signal_connect (window, "button-press-event",
+                    G_CALLBACK (button_press_cb), &data);
+  g_signal_connect (window, "button-release-event",
+                    G_CALLBACK (button_release_cb), &data);
+  g_signal_connect (window, "multidevice-event",
+                    G_CALLBACK (multidevice_cb), &data);
+  g_signal_connect (window, "expose-event",
+                    G_CALLBACK (expose_cb), &data);
+
+  gtk_widget_show (window);
+
+  gtk_main ();
+
+  return 0;
+}
diff --git a/tests/multidevice/testcoordinates.c b/tests/multidevice/testcoordinates.c
new file mode 100644
index 0000000..dc43645
--- /dev/null
+++ b/tests/multidevice/testcoordinates.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <gtk/gtk.h>
+#include "testcoordinatewidget.h"
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window, *widget;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+  widget = test_coordinate_widget_new ();
+  gtk_container_add (GTK_CONTAINER (window), widget);
+
+  g_signal_connect (window, "delete-event",
+                    G_CALLBACK (gtk_main_quit), NULL);
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  return 0;
+}
diff --git a/tests/multidevice/testcoordinatewidget.c b/tests/multidevice/testcoordinatewidget.c
new file mode 100644
index 0000000..f9f3887
--- /dev/null
+++ b/tests/multidevice/testcoordinatewidget.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include "testcoordinatewidget.h"
+
+#define TEST_COORDINATE_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetPrivate))
+
+typedef struct TestCoordinateWidgetPrivate TestCoordinateWidgetPrivate;
+
+struct TestCoordinateWidgetPrivate
+{
+  GtkDeviceGroup *group;
+  GHashTable *coordinates;
+};
+
+static void test_coordinate_widget_class_init        (TestCoordinateWidgetClass *klass);
+static void test_coordinate_widget_init              (TestCoordinateWidget      *coordinate);
+
+static void test_coordinate_widget_destroy           (GtkObject          *object);
+
+static gboolean test_coordinate_widget_button_press   (GtkWidget          *widget,
+                                                       GdkEventButton     *event);
+static gboolean test_coordinate_widget_button_release (GtkWidget          *widget,
+                                                       GdkEventButton     *event);
+static gboolean test_coordinate_widget_expose         (GtkWidget          *widget,
+                                                       GdkEventExpose     *event);
+
+static void     test_coordinate_widget_multidevice_event (GtkWidget           *widget,
+                                                          GtkDeviceGroup      *group,
+                                                          GtkMultiDeviceEvent *event);
+
+G_DEFINE_TYPE (TestCoordinateWidget, test_coordinate_widget, GTK_TYPE_DRAWING_AREA)
+
+
+static void
+test_coordinate_widget_class_init (TestCoordinateWidgetClass *klass)
+{
+  GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->destroy = test_coordinate_widget_destroy;
+
+  widget_class->button_press_event = test_coordinate_widget_button_press;
+  widget_class->button_release_event = test_coordinate_widget_button_release;
+  widget_class->expose_event = test_coordinate_widget_expose;
+
+  g_type_class_add_private (klass, sizeof (TestCoordinateWidgetPrivate));
+}
+
+static void
+test_coordinate_widget_init (TestCoordinateWidget *coord)
+{
+  TestCoordinateWidgetPrivate *priv;
+  GtkWidget *widget;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (coord);
+  widget = GTK_WIDGET (coord);
+
+  priv->coordinates = g_hash_table_new_full (g_direct_hash,
+                                             g_direct_equal,
+                                             (GDestroyNotify) g_object_unref,
+                                             (GDestroyNotify) g_free);
+
+  gtk_widget_add_events (widget,
+                         (GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                          GDK_POINTER_MOTION_MASK |
+                          GDK_ENTER_NOTIFY_MASK |
+                          GDK_LEAVE_NOTIFY_MASK));
+
+  priv->group = gtk_widget_create_device_group (GTK_WIDGET (coord));
+  gtk_widget_set_support_multidevice (widget, TRUE);
+
+  /* Multidevice events are not exposed through GtkWidgetClass */
+  g_signal_connect (coord, "multidevice-event",
+                    G_CALLBACK (test_coordinate_widget_multidevice_event), NULL);
+}
+
+static void
+test_coordinate_widget_destroy (GtkObject *object)
+{
+  TestCoordinateWidgetPrivate *priv;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (object);
+
+  if (priv->coordinates)
+    {
+      g_hash_table_destroy (priv->coordinates);
+      priv->coordinates = NULL;
+    }
+
+  if (priv->group)
+    {
+      gtk_widget_remove_device_group (GTK_WIDGET (object), priv->group);
+      priv->group = NULL;
+    }
+
+  GTK_OBJECT_CLASS (test_coordinate_widget_parent_class)->destroy (object);
+}
+
+static gboolean
+test_coordinate_widget_button_press (GtkWidget      *widget,
+                                     GdkEventButton *event)
+{
+  TestCoordinateWidgetPrivate *priv;
+  GdkDevice *device;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
+  device = gdk_event_get_device ((GdkEvent *) event);
+
+  gtk_device_group_add_device (priv->group, device);
+
+  return FALSE;
+}
+
+static gboolean
+test_coordinate_widget_button_release (GtkWidget      *widget,
+                                       GdkEventButton *event)
+{
+  TestCoordinateWidgetPrivate *priv;
+  GdkDevice *device;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
+  device = gdk_event_get_device ((GdkEvent *) event);
+
+  gtk_device_group_remove_device (priv->group, device);
+
+  return FALSE;
+}
+
+static void
+test_coordinate_widget_multidevice_event (GtkWidget           *widget,
+                                          GtkDeviceGroup      *group,
+                                          GtkMultiDeviceEvent *event)
+{
+  TestCoordinateWidgetPrivate *priv;
+  GdkEventMotion *ev;
+  GdkPoint *point;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
+
+  if (event->type == GTK_EVENT_DEVICE_REMOVED)
+    g_hash_table_remove (priv->coordinates, event->updated_device);
+  else
+    {
+      point = g_hash_table_lookup (priv->coordinates, event->updated_device);
+      ev = event->updated_event;
+
+      if (G_UNLIKELY (!point))
+        {
+          point = g_new (GdkPoint, 1);
+          g_hash_table_insert (priv->coordinates,
+                               g_object_ref (event->updated_device),
+                               point);
+        }
+
+      point->x = ev->x;
+      point->y = ev->y;
+    }
+
+  gtk_widget_queue_draw (widget);
+}
+
+static gboolean
+test_coordinate_widget_expose (GtkWidget      *widget,
+                               GdkEventExpose *event)
+{
+  TestCoordinateWidgetPrivate *priv;
+  GList *coords, *c;
+  cairo_t *cr;
+
+  priv = TEST_COORDINATE_WIDGET_GET_PRIVATE (widget);
+  cr = gdk_cairo_create (widget->window);
+
+  coords = g_hash_table_get_values (priv->coordinates);
+
+  cairo_set_source_rgb (cr, 1., 1., 1.);
+  cairo_rectangle (cr,
+                   widget->allocation.x,
+                   widget->allocation.y,
+                   widget->allocation.width,
+                   widget->allocation.height);
+  cairo_fill (cr);
+
+  cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
+
+  for (c = coords; c; c = c->next)
+    {
+      GdkPoint *point;
+
+      point = c->data;
+
+      cairo_move_to (cr, point->x, 0);
+      cairo_rel_line_to (cr, 0, widget->allocation.height);
+
+      cairo_move_to (cr, 0, point->y);
+      cairo_rel_line_to (cr, widget->allocation.width, 0);
+    }
+
+  cairo_stroke (cr);
+
+  cairo_set_source_rgba (cr, 0.5, 0., 0., 0.5);
+
+  if (g_list_length (coords) > 1)
+    {
+      for (c = coords; c; c = c->next)
+        {
+          GdkPoint *point;
+
+          point = c->data;
+
+          cairo_line_to (cr, point->x, point->y);
+        }
+
+      cairo_close_path (cr);
+      cairo_fill_preserve (cr);
+
+      cairo_set_source_rgba (cr, 0., 0., 0.5, 0.5);
+      cairo_stroke (cr);
+    }
+
+  cairo_destroy (cr);
+
+  g_list_free (coords);
+
+  return TRUE;
+}
+
+GtkWidget *
+test_coordinate_widget_new (void)
+{
+  return g_object_new (TEST_TYPE_COORDINATE_WIDGET, NULL);
+}
diff --git a/tests/multidevice/testcoordinatewidget.h b/tests/multidevice/testcoordinatewidget.h
new file mode 100644
index 0000000..bfa764d
--- /dev/null
+++ b/tests/multidevice/testcoordinatewidget.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#ifndef __TEST_COORDINATE_WIDGET_H__
+#define __TEST_COORDINATE_WIDGET_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TEST_TYPE_COORDINATE_WIDGET         (test_coordinate_widget_get_type ())
+#define TEST_COORDINATE_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidget))
+#define TEST_COORDINATE_WIDGET_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST    ((c), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetClass))
+#define TEST_IS_COORDINATE_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_COORDINATE_WIDGET))
+#define TEST_IS_COORDINATE_WIDGET_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE    ((c), TEST_TYPE_COORDINATE_WIDGET))
+#define TEST_COORDINATE_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS  ((o), TEST_TYPE_COORDINATE_WIDGET, TestCoordinateWidgetClass))
+
+typedef struct TestCoordinateWidget TestCoordinateWidget;
+typedef struct TestCoordinateWidgetClass TestCoordinateWidgetClass;
+
+struct TestCoordinateWidget
+{
+  GtkDrawingArea parent_instance;
+};
+
+struct TestCoordinateWidgetClass
+{
+  GtkDrawingAreaClass parent_class;
+};
+
+
+GType       test_coordinate_widget_get_type (void) G_GNUC_CONST;
+
+GtkWidget * test_coordinate_widget_new      (void);
+
+
+G_END_DECLS
+
+#endif /* __TEST_COORDINATE_WIDGET_H__ */
diff --git a/tests/multidevice/testphotoalbum.c b/tests/multidevice/testphotoalbum.c
new file mode 100644
index 0000000..008e5e5
--- /dev/null
+++ b/tests/multidevice/testphotoalbum.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <gtk/gtk.h>
+#include "testphotoalbumwidget.h"
+
+static void
+add_image (TestPhotoAlbumWidget *album,
+           const gchar          *image_path)
+{
+  GdkPixbuf *pixbuf;
+
+  pixbuf = gdk_pixbuf_new_from_file_at_size (image_path,
+                                             200, -1,
+                                             NULL);
+  if (pixbuf)
+    {
+      test_photo_album_widget_add_image (album, pixbuf);
+      g_object_unref (pixbuf);
+    }
+}
+
+static void
+read_directory (GFile                *directory,
+                TestPhotoAlbumWidget *album)
+{
+  GFileEnumerator *enumerator;
+  GError *error = NULL;
+  GFileInfo *info;
+
+  enumerator = g_file_enumerate_children (directory,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                          G_FILE_QUERY_INFO_NONE,
+                                          NULL,
+                                          &error);
+
+  if (error)
+    {
+      g_warning ("%s\n", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
+    {
+      GFile *file;
+      gchar *path;
+
+      file = g_file_get_child (directory, g_file_info_get_name (info));
+
+      path = g_file_get_path (file);
+      add_image (album, path);
+      g_free (path);
+
+      g_object_unref (file);
+    }
+
+  g_file_enumerator_close (enumerator, NULL, NULL);
+  g_object_unref (enumerator);
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window, *widget;
+  GFile *dir;
+
+  if (argc != 2)
+    {
+      g_print ("USAGE: %s <path-to-directory-with-images>\n", argv[0]);
+      return -1;
+    }
+
+  gtk_init (&argc, &argv);
+
+  dir = g_file_new_for_commandline_arg (argv[1]);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+  widget = test_photo_album_widget_new ();
+  gtk_container_add (GTK_CONTAINER (window), widget);
+
+  g_signal_connect (window, "delete-event",
+                    G_CALLBACK (gtk_main_quit), NULL);
+
+  read_directory (dir, TEST_PHOTO_ALBUM_WIDGET (widget));
+
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  return 0;
+}
diff --git a/tests/multidevice/testphotoalbumwidget.c b/tests/multidevice/testphotoalbumwidget.c
new file mode 100644
index 0000000..b530069
--- /dev/null
+++ b/tests/multidevice/testphotoalbumwidget.c
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include "testphotoalbumwidget.h"
+#include <math.h>
+
+#define TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetPrivate))
+
+typedef struct TestPhotoAlbumWidgetPrivate TestPhotoAlbumWidgetPrivate;
+typedef struct TestPhoto TestPhoto;
+
+struct TestPhoto
+{
+  GdkPixbuf *pixbuf;
+  GtkDeviceGroup *group;
+  gdouble center_x;
+  gdouble center_y;
+  gdouble x;
+  gdouble y;
+  gdouble angle;
+  gdouble zoom;
+
+  GdkRegion *region;
+
+  gdouble base_zoom;
+  gdouble base_angle;
+  gdouble initial_distance;
+  gdouble initial_angle;
+};
+
+struct TestPhotoAlbumWidgetPrivate
+{
+  GPtrArray *photos;
+};
+
+static GQuark quark_group_photo = 0;
+
+
+static void test_photo_album_widget_class_init (TestPhotoAlbumWidgetClass *klass);
+static void test_photo_album_widget_init       (TestPhotoAlbumWidget      *album);
+
+static void test_photo_album_widget_destroy    (GtkObject          *object);
+
+static gboolean test_photo_album_widget_button_press      (GtkWidget           *widget,
+                                                           GdkEventButton      *event);
+static gboolean test_photo_album_widget_button_release    (GtkWidget           *widget,
+                                                           GdkEventButton      *event);
+static void     test_photo_album_widget_multidevice_event (GtkWidget           *widget,
+                                                           GtkDeviceGroup      *group,
+                                                           GtkMultiDeviceEvent *event);
+static gboolean test_photo_album_widget_expose            (GtkWidget           *widget,
+                                                           GdkEventExpose      *event);
+
+G_DEFINE_TYPE (TestPhotoAlbumWidget, test_photo_album_widget, GTK_TYPE_DRAWING_AREA)
+
+
+static void
+test_photo_album_widget_class_init (TestPhotoAlbumWidgetClass *klass)
+{
+  GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->destroy = test_photo_album_widget_destroy;
+
+  widget_class->button_press_event = test_photo_album_widget_button_press;
+  widget_class->button_release_event = test_photo_album_widget_button_release;
+  widget_class->expose_event = test_photo_album_widget_expose;
+
+  g_type_class_add_private (klass, sizeof (TestPhotoAlbumWidgetPrivate));
+
+  quark_group_photo = g_quark_from_static_string ("group-photo");
+}
+
+static void
+test_photo_album_widget_init (TestPhotoAlbumWidget *album)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+  GtkWidget *widget;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
+  widget = GTK_WIDGET (album);
+
+  priv->photos = g_ptr_array_new ();
+
+  gtk_widget_add_events (widget,
+                         (GDK_POINTER_MOTION_MASK |
+                          GDK_BUTTON_MOTION_MASK |
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK));
+
+  gtk_widget_set_support_multidevice (widget, TRUE);
+
+  /* Multidevice events are not exposed through GtkWidgetClass */
+  g_signal_connect (album, "multidevice-event",
+                    G_CALLBACK (test_photo_album_widget_multidevice_event), NULL);
+}
+
+static void
+calculate_rotated_point (gdouble  angle,
+                         gdouble  zoom,
+                         gdouble  center_x,
+                         gdouble  center_y,
+                         gdouble  point_x,
+                         gdouble  point_y,
+                         gdouble *ret_x,
+                         gdouble *ret_y)
+{
+  gdouble distance, xd, yd, ang;
+
+  xd = center_x - point_x;
+  yd = center_y - point_y;
+
+  if (xd == 0 && yd == 0)
+    {
+      *ret_x = center_x;
+      *ret_y = center_y;
+      return;
+    }
+
+  distance = sqrt ((xd * xd) + (yd * yd));
+  distance *= zoom;
+
+  ang = atan2 (xd, yd);
+
+  /* Invert angle */
+  ang = (2 * G_PI) - ang;
+
+  /* Shift it 270° */
+  ang += 3 * (G_PI / 2);
+
+  /* And constraint it to 0°-360° */
+  ang = fmod (ang, 2 * G_PI);
+  ang += angle;
+
+  *ret_x = center_x + (distance * cos (ang));
+  *ret_y = center_y + (distance * sin (ang));
+}
+
+static void
+allocate_photo_region (TestPhoto *photo)
+{
+  GdkPoint points[4];
+  gint width, height, i;
+
+  width = gdk_pixbuf_get_width (photo->pixbuf);
+  height = gdk_pixbuf_get_height (photo->pixbuf);
+
+  /* Top/left */
+  points[0].x = photo->x - photo->center_x;
+  points[0].y = photo->y - photo->center_y;
+
+  /* Top/right */
+  points[1].x = photo->x - photo->center_x + width;
+  points[1].y = photo->y - photo->center_y;
+
+  /* Bottom/right */
+  points[2].x = photo->x - photo->center_x + width;
+  points[2].y = photo->y - photo->center_y + height;
+
+  /* Bottom/left */
+  points[3].x = photo->x - photo->center_x;
+  points[3].y = photo->y - photo->center_y + height;
+
+  for (i = 0; i < 4; i++)
+    {
+      gdouble ret_x, ret_y;
+
+      calculate_rotated_point (photo->angle,
+                               photo->zoom,
+                               photo->x,
+                               photo->y,
+                               (gdouble) points[i].x,
+                               (gdouble) points[i].y,
+                               &ret_x,
+                               &ret_y);
+
+      points[i].x = (gint) ret_x;
+      points[i].y = (gint) ret_y;
+    }
+
+  if (photo->region)
+    gdk_region_destroy (photo->region);
+
+  photo->region = gdk_region_polygon (points, 4, GDK_WINDING_RULE);
+}
+
+static TestPhoto *
+test_photo_new (TestPhotoAlbumWidget *album,
+                GdkPixbuf            *pixbuf)
+{
+  TestPhoto *photo;
+  static gdouble angle = 0;
+
+  photo = g_slice_new0 (TestPhoto);
+  photo->pixbuf = g_object_ref (pixbuf);
+  photo->group = gtk_widget_create_device_group (GTK_WIDGET (album));
+  g_object_set_qdata (G_OBJECT (photo->group), quark_group_photo, photo);
+
+  photo->center_x = 0;
+  photo->center_y = 0;
+  photo->x = 0;
+  photo->y = 0;
+  photo->angle = angle;
+  photo->zoom = 1.0;
+
+  angle += 0.1;
+
+  allocate_photo_region (photo);
+
+  return photo;
+}
+
+static void
+test_photo_free (TestPhoto            *photo,
+                 TestPhotoAlbumWidget *album)
+{
+  g_object_unref (photo->pixbuf);
+  gtk_widget_remove_device_group (GTK_WIDGET (album), photo->group);
+
+  g_slice_free (TestPhoto, photo);
+}
+
+static void
+test_photo_raise (TestPhoto            *photo,
+                  TestPhotoAlbumWidget *album)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
+  g_ptr_array_remove (priv->photos, photo);
+  g_ptr_array_add (priv->photos, photo);
+}
+
+static void
+test_photo_album_widget_destroy (GtkObject *object)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (object);
+
+  if (priv->photos)
+    {
+      g_ptr_array_foreach (priv->photos, (GFunc) test_photo_free, object);
+      g_ptr_array_free (priv->photos, TRUE);
+      priv->photos = NULL;
+    }
+
+  GTK_OBJECT_CLASS (test_photo_album_widget_parent_class)->destroy (object);
+}
+
+static TestPhoto *
+find_photo_at_position (TestPhotoAlbumWidget *album,
+                        gdouble               x,
+                        gdouble               y)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+  TestPhoto *photo;
+  gint i;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
+
+  for (i = priv->photos->len - 1; i >= 0; i--)
+    {
+      photo = g_ptr_array_index (priv->photos, i);
+
+      if (gdk_region_point_in (photo->region, (gint) x, (gint) y))
+        return photo;
+    }
+
+  return NULL;
+}
+
+static gboolean
+test_photo_album_widget_button_press (GtkWidget      *widget,
+                                      GdkEventButton *event)
+{
+  TestPhoto *photo;
+
+  photo = find_photo_at_position (TEST_PHOTO_ALBUM_WIDGET (widget), event->x, event->y);
+
+  if (!photo)
+    return FALSE;
+
+  test_photo_raise (photo, TEST_PHOTO_ALBUM_WIDGET (widget));
+  gtk_device_group_add_device (photo->group, event->device);
+
+  return TRUE;
+}
+
+static gboolean
+test_photo_album_widget_button_release (GtkWidget      *widget,
+                                        GdkEventButton *event)
+{
+  GtkDeviceGroup *group;
+
+  group = gtk_widget_get_group_for_device (widget, event->device);
+
+  if (group)
+    gtk_device_group_remove_device (group, event->device);
+
+  return TRUE;
+}
+
+static void
+test_photo_album_widget_multidevice_event (GtkWidget           *widget,
+                                           GtkDeviceGroup      *group,
+                                           GtkMultiDeviceEvent *event)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+  GdkRegion *region;
+  TestPhoto *photo;
+  gboolean new_center = FALSE;
+  gboolean new_position = FALSE;
+  gdouble event_x, event_y;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (widget);
+  photo = g_object_get_qdata (G_OBJECT (group), quark_group_photo);
+
+  region = gdk_region_copy (photo->region);
+
+  if (event->n_events == 1)
+    {
+      if (event->type == GTK_EVENT_DEVICE_REMOVED)
+        {
+          /* Device was just removed, unset zoom/angle info */
+          photo->base_zoom = 0;
+          photo->base_angle = 0;
+          photo->initial_angle = 0;
+          photo->initial_distance = 0;
+          new_center = TRUE;
+        }
+      else if (event->type == GTK_EVENT_DEVICE_ADDED)
+        new_center = TRUE;
+
+      event_x = event->events[0]->x;
+      event_y = event->events[0]->y;
+      new_position = TRUE;
+    }
+  else if (event->n_events == 2)
+    {
+      gdouble distance, angle;
+
+      gdk_events_get_center ((GdkEvent *) event->events[0],
+                             (GdkEvent *) event->events[1],
+                             &event_x, &event_y);
+
+      gdk_events_get_distance ((GdkEvent *) event->events[0],
+                               (GdkEvent *) event->events[1],
+                               &distance);
+
+      gdk_events_get_angle ((GdkEvent *) event->events[0],
+                            (GdkEvent *) event->events[1],
+                            &angle);
+
+      if (event->type == GTK_EVENT_DEVICE_ADDED)
+        {
+          photo->base_zoom = photo->zoom;
+          photo->base_angle = photo->angle;
+          photo->initial_angle = angle;
+          photo->initial_distance = distance;
+          new_center = TRUE;
+        }
+
+      photo->zoom = photo->base_zoom * (distance / photo->initial_distance);
+      photo->angle = photo->base_angle + (angle - photo->initial_angle);
+      new_position = TRUE;
+    }
+
+  if (new_center)
+    {
+      gdouble origin_x, origin_y;
+
+      origin_x = photo->x - photo->center_x;
+      origin_y = photo->y - photo->center_y;
+
+      calculate_rotated_point (- photo->angle,
+                               1 / photo->zoom,
+                               photo->x - origin_x,
+                               photo->y - origin_y,
+                               event_x - origin_x,
+                               event_y - origin_y,
+                               &photo->center_x,
+                               &photo->center_y);
+    }
+
+  if (new_position)
+    {
+      photo->x = event_x;
+      photo->y = event_y;
+    }
+
+  allocate_photo_region (photo);
+
+  gdk_region_union (region, photo->region);
+  gdk_region_shrink (region, -4, -4);
+
+  gdk_window_invalidate_region (widget->window, region, FALSE);
+}
+
+static gboolean
+test_photo_album_widget_expose (GtkWidget      *widget,
+                                GdkEventExpose *event)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+  cairo_t *cr;
+  gint i;
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (widget);
+  cr = gdk_cairo_create (widget->window);
+
+  for (i = 0; i < priv->photos->len; i++)
+    {
+      TestPhoto *photo = g_ptr_array_index (priv->photos, i);
+      GdkRegion *region;
+      gint width, height;
+
+      region = gdk_region_copy (photo->region);
+      gdk_region_shrink (region, -4, -4);
+
+      gdk_region_intersect (region, event->region);
+
+      if (gdk_region_empty (region))
+        {
+          gdk_region_destroy (region);
+          continue;
+        }
+
+      width = gdk_pixbuf_get_width (photo->pixbuf);
+      height = gdk_pixbuf_get_height (photo->pixbuf);
+
+      cairo_save (cr);
+
+      gdk_cairo_region (cr, region);
+      cairo_clip (cr);
+
+      cairo_translate (cr, photo->x, photo->y);
+
+      cairo_scale (cr, photo->zoom, photo->zoom);
+      cairo_rotate (cr, photo->angle);
+      gdk_cairo_set_source_pixbuf (cr,
+                                   photo->pixbuf,
+                                   - photo->center_x,
+                                   - photo->center_y);
+
+      cairo_rectangle (cr,
+                       - photo->center_x,
+                       - photo->center_y,
+                       width, height);
+      cairo_fill_preserve (cr);
+
+      cairo_set_source_rgb (cr, 0., 0., 0.);
+      cairo_stroke (cr);
+
+      cairo_restore (cr);
+
+      gdk_region_destroy (region);
+    }
+
+  cairo_destroy (cr);
+
+  return TRUE;
+}
+
+GtkWidget *
+test_photo_album_widget_new (void)
+{
+  return g_object_new (TEST_TYPE_PHOTO_ALBUM_WIDGET, NULL);
+}
+
+void
+test_photo_album_widget_add_image (TestPhotoAlbumWidget *album,
+                                   GdkPixbuf            *pixbuf)
+{
+  TestPhotoAlbumWidgetPrivate *priv;
+  TestPhoto *photo;
+
+  g_return_if_fail (TEST_IS_PHOTO_ALBUM_WIDGET (album));
+  g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+  priv = TEST_PHOTO_ALBUM_WIDGET_GET_PRIVATE (album);
+
+  photo = test_photo_new (album, pixbuf);
+  g_ptr_array_add (priv->photos, photo);
+
+  if (gtk_widget_get_realized (album) &&
+      gtk_widget_is_drawable (album))
+    gdk_window_invalidate_region (GTK_WIDGET (album)->window,
+                                  photo->region,
+                                  FALSE);
+}
diff --git a/tests/multidevice/testphotoalbumwidget.h b/tests/multidevice/testphotoalbumwidget.h
new file mode 100644
index 0000000..0a36378
--- /dev/null
+++ b/tests/multidevice/testphotoalbumwidget.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2009 Carlos Garnacho  <carlosg gnome org>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#ifndef __TEST_PHOTO_ALBUM_WIDGET_H__
+#define __TEST_PHOTO_ALBUM_WIDGET_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TEST_TYPE_PHOTO_ALBUM_WIDGET         (test_photo_album_widget_get_type ())
+#define TEST_PHOTO_ALBUM_WIDGET(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidget))
+#define TEST_PHOTO_ALBUM_WIDGET_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST    ((c), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetClass))
+#define TEST_IS_PHOTO_ALBUM_WIDGET(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET))
+#define TEST_IS_PHOTO_ALBUM_WIDGET_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE    ((c), TEST_TYPE_PHOTO_ALBUM_WIDGET))
+#define TEST_PHOTO_ALBUM_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS  ((o), TEST_TYPE_PHOTO_ALBUM_WIDGET, TestPhotoAlbumWidgetClass))
+
+typedef struct TestPhotoAlbumWidget TestPhotoAlbumWidget;
+typedef struct TestPhotoAlbumWidgetClass TestPhotoAlbumWidgetClass;
+
+struct TestPhotoAlbumWidget
+{
+  GtkDrawingArea parent_instance;
+};
+
+struct TestPhotoAlbumWidgetClass
+{
+  GtkDrawingAreaClass parent_class;
+};
+
+
+GType       test_photo_album_widget_get_type  (void) G_GNUC_CONST;
+
+GtkWidget * test_photo_album_widget_new       (void);
+
+void        test_photo_album_widget_add_image (TestPhotoAlbumWidget *album,
+                                               GdkPixbuf            *pixbuf);
+
+
+G_END_DECLS
+
+#endif /* __TEST_PHOTO_ALBUM_WIDGET_H__ */



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