diff --git a/configure.ac b/configure.ac index 58584096..fa817b58 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,9 @@ PKG_CHECK_MODULES(EXT, gtk+-3.0 gdk-3.0 gdk-pixbuf-2.0 sm ice alsa librsvg-2.0) PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0` AC_SUBST(PYGTK_DEFSDIR) +GLIB_MKENUMS=`$PKG_CONFIG glib-2.0 --variable=glib_mkenums` +AC_SUBST(GLIB_MKENUMS) + # Setup GETTEXT # ALL_LINGUAS="af am ar aym bg bi bn_IN bn ca cs da de dz el en es fa_AF fa ff fil fr gu ha he hi ht hu id ig is it ja km ko kos mg mi mk ml mn mr ms mvo nb ne nl pa pap pl ps pt_BR pt quz ro ru rw sd si sk sl sq sv sw ta te th tr tvl tzo ug ur vi wa yo zh_CN zh_TW" @@ -45,6 +48,7 @@ src/sugar3/Makefile src/sugar3/activity/Makefile src/sugar3/bundle/Makefile src/sugar3/graphics/Makefile +src/sugar3/event-controller/Makefile src/sugar3/presence/Makefile src/sugar3/datastore/Makefile src/sugar3/dispatch/Makefile diff --git a/src/sugar3/Makefile.am b/src/sugar3/Makefile.am index 9b2a97e3..7cbffa6e 100644 --- a/src/sugar3/Makefile.am +++ b/src/sugar3/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = activity bundle graphics presence datastore dispatch +SUBDIRS = event-controller activity bundle graphics presence datastore dispatch sugardir = $(pythondir)/sugar3 sugar_PYTHON = \ diff --git a/src/sugar3/event-controller/Makefile.am b/src/sugar3/event-controller/Makefile.am new file mode 100644 index 00000000..d56f12ea --- /dev/null +++ b/src/sugar3/event-controller/Makefile.am @@ -0,0 +1,51 @@ +lib_LTLIBRARIES = libsugar-eventcontroller.la + +libsugar_eventcontroller_la_LDFLAGS = $(LDADD) +libsugar_eventcontroller_la_LIBADD = $(EXT_LIBS) + +libsugar_eventcontrollerincludedir = $(includedir)/sugar-3.0/event-controller/ + +libsugar_eventcontroller_la_CFLAGS = \ + $(EXT_CFLAGS) \ + $(WARN_FLAGS) \ + -DSUGAR_TOOLKIT_COMPILATION + +eventcontroller_h_sources = \ + sugar-event-controller.h \ + sugar-event-controllers.h \ + sugar-long-press-controller.h \ + sugar-rotate-controller.h \ + sugar-swipe-controller.h \ + sugar-zoom-controller.h + +eventcontroller_c_sources = \ + sugar-event-controller.c \ + sugar-long-press-controller.c \ + sugar-rotate-controller.c \ + sugar-swipe-controller.c \ + sugar-zoom-controller.c + + +libsugar_eventcontroller_la_SOURCES = \ + $(BUILT_SOURCES) \ + $(eventcontroller_h_sources) \ + $(eventcontroller_c_sources) + +libsugar_eventcontrollerinclude_HEADERS = \ + $(eventcontroller_h_sources) + +BUILT_SOURCES = \ + sugar-enum-types.c \ + sugar-enum-types.h + +sugar-enum-types.h: sugar-enum-types.h.template $(eventcontroller_h_sources) + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template sugar-enum-types.h.template $(eventcontroller_h_sources)) > $@ + +sugar-enum-types.c: sugar-enum-types.c.template $(eventcontroller_h_sources) + $(AM_V_GEN) (cd $(srcdir) && $(GLIB_MKENUMS) --template sugar-enum-types.c.template $(eventcontroller_h_sources)) > $@ + +EXTRA_DIST = \ + sugar-enum-types.c.template \ + sugar-enum-types.h.template + +CLEANFILES = $(BUILT_SOURCES) diff --git a/src/sugar3/event-controller/sugar-enum-types.c.template b/src/sugar3/event-controller/sugar-enum-types.c.template new file mode 100644 index 00000000..cb72157f --- /dev/null +++ b/src/sugar3/event-controller/sugar-enum-types.c.template @@ -0,0 +1,35 @@ +/*** BEGIN file-header ***/ +#include "sugar-event-controllers.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/src/sugar3/event-controller/sugar-enum-types.h.template b/src/sugar3/event-controller/sugar-enum-types.h.template new file mode 100644 index 00000000..bba396de --- /dev/null +++ b/src/sugar3/event-controller/sugar-enum-types.h.template @@ -0,0 +1,28 @@ +/*** BEGIN file-header ***/ +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_TYPE_BUILTINS_H__ +#define __SUGAR_TYPE_BUILTINS_H__ + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __SUGAR_TYPE_BUILTINS_H__ */ +/*** END file-tail ***/ diff --git a/src/sugar3/event-controller/sugar-event-controller.c b/src/sugar3/event-controller/sugar-event-controller.c new file mode 100644 index 00000000..9e15650b --- /dev/null +++ b/src/sugar3/event-controller/sugar-event-controller.c @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#include "sugar-event-controller.h" +#include "sugar-enum-types.h" + +typedef struct _SugarControllerItem SugarControllerItem; +typedef struct _SugarControllerWidgetData SugarControllerWidgetData; + +enum { + PROP_STATE = 1 +}; + +enum { + STARTED, + FINISHED, + LAST_SIGNAL +}; + +struct _SugarControllerItem +{ + SugarEventController *controller; + SugarEventControllerFlags flags; + guint notify_handler_id; +}; + +struct _SugarControllerWidgetData +{ + GArray *controllers; + guint event_handler_id; + GtkWidget *widget; + SugarEventController *current_exclusive; +}; + +G_DEFINE_ABSTRACT_TYPE (SugarEventController, sugar_event_controller, G_TYPE_OBJECT) + +static guint signals[LAST_SIGNAL] = { 0 }; +static GQuark quark_widget_controller_data = 0; + +static void +sugar_event_controller_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_STATE: + { + SugarEventControllerState state; + + state = sugar_event_controller_get_state (SUGAR_EVENT_CONTROLLER (object)); + g_value_set_enum (value, state); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sugar_event_controller_class_init (SugarEventControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sugar_event_controller_get_property; + + g_object_class_install_property (object_class, + PROP_STATE, + g_param_spec_enum ("state", + "State", + "Controller state", + SUGAR_TYPE_EVENT_CONTROLLER_STATE, + SUGAR_EVENT_CONTROLLER_STATE_NONE, + G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + signals[STARTED] = + g_signal_new ("started", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SugarEventControllerClass, started), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[FINISHED] = + g_signal_new ("finished", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SugarEventControllerClass, finished), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + quark_widget_controller_data = g_quark_from_static_string ("sugar-widget-controller-data"); +} + +static void +sugar_event_controller_init (SugarEventController *controller) +{ +} + +static gboolean +_sugar_event_controller_widget_event (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + SugarControllerWidgetData *data; + gboolean handled = FALSE; + guint i; + + data = g_object_get_qdata (G_OBJECT (widget), + quark_widget_controller_data); + + if (!data || !data->controllers || data->controllers->len == 0) + return FALSE; + + for (i = 0; i < data->controllers->len; i++) + { + SugarEventControllerState state; + SugarControllerItem *item; + + item = &g_array_index (data->controllers, SugarControllerItem, i); + + if (data->current_exclusive && + data->current_exclusive != item->controller) + continue; + + if (!sugar_event_controller_handle_event (item->controller, event)) + continue; + + state = sugar_event_controller_get_state (item->controller); + + /* Consider events handled once the + * controller recognizes the action + */ + if (state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) + handled = TRUE; + } + + return handled; +} + +gboolean +sugar_event_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + SugarEventControllerClass *controller_class; + + g_return_val_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + controller_class = SUGAR_EVENT_CONTROLLER_GET_CLASS (controller); + + if (!controller_class->handle_event) + return FALSE; + + return controller_class->handle_event (controller, event); +} + +static SugarControllerWidgetData * +_sugar_event_controller_widget_data_new (GtkWidget *widget) +{ + SugarControllerWidgetData *data; + + data = g_slice_new0 (SugarControllerWidgetData); + data->widget = widget; + data->controllers = g_array_new (FALSE, TRUE, sizeof (SugarControllerItem)); + data->event_handler_id = + g_signal_connect (widget, "event", + G_CALLBACK (_sugar_event_controller_widget_event), + NULL); + return data; +} + +static void +_sugar_event_controller_widget_data_free (SugarControllerWidgetData *data) +{ + guint i; + + if (g_signal_handler_is_connected (data->widget, data->event_handler_id)) + g_signal_handler_disconnect (data->widget, data->event_handler_id); + + for (i = 0; i < data->controllers->len; i++) + { + SugarControllerItem *item; + + item = &g_array_index (data->controllers, SugarControllerItem, i); + g_signal_handler_disconnect (item->controller, item->notify_handler_id); + g_object_unref (item->controller); + } + + g_array_unref (data->controllers); + g_slice_free (SugarControllerWidgetData, data); +} + +static void +_sugar_event_controller_state_notify (SugarEventController *controller, + GParamSpec *pspec, + GtkWidget *widget) +{ + SugarControllerWidgetData *data; + SugarControllerItem *item, *ptr; + SugarEventControllerState state; + guint i; + + data = g_object_get_qdata (G_OBJECT (widget), quark_widget_controller_data); + state = sugar_event_controller_get_state (controller); + + if (!data) + return; + + if (state == SUGAR_EVENT_CONTROLLER_STATE_NONE && + data->current_exclusive == controller) + data->current_exclusive = NULL; + else if (!data->current_exclusive && + state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) + { + for (i = 0; i < data->controllers->len; i++) + { + ptr = &g_array_index (data->controllers, SugarControllerItem, i); + if (ptr->controller == controller) + { + item = ptr; + break; + } + } + + if (!item) + return; + + if ((item->flags & SUGAR_EVENT_CONTROLLER_FLAG_EXCLUSIVE) != 0) + { + data->current_exclusive = controller; + + /* Reset all other controllers */ + for (i = 0; i < data->controllers->len; i++) + { + ptr = &g_array_index (data->controllers, SugarControllerItem, i); + + if (ptr->controller != controller) + sugar_event_controller_reset (ptr->controller); + } + } + } +} + +gboolean +sugar_event_controller_attach (SugarEventController *controller, + GtkWidget *widget, + SugarEventControllerFlags flags) +{ + SugarControllerWidgetData *data; + SugarControllerItem *ptr, item; + guint i; + + g_return_val_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + data = g_object_get_qdata (G_OBJECT (widget), quark_widget_controller_data); + + if (!data) + { + data = _sugar_event_controller_widget_data_new (widget); + g_object_set_qdata_full (G_OBJECT (widget), + quark_widget_controller_data, data, + (GDestroyNotify) _sugar_event_controller_widget_data_free); + } + + for (i = 0; i < data->controllers->len; i++) + { + ptr = &g_array_index (data->controllers, SugarControllerItem, i); + + if (ptr->controller == controller) + return FALSE; + } + + item.controller = g_object_ref (controller); + item.flags = flags; + item.notify_handler_id = g_signal_connect (controller, "notify::state", + G_CALLBACK (_sugar_event_controller_state_notify), + widget); + g_array_append_val (data->controllers, item); + + return TRUE; +} + +gboolean +sugar_event_controller_detach (SugarEventController *controller, + GtkWidget *widget) +{ + SugarControllerWidgetData *data; + SugarControllerItem *item; + gboolean removed = FALSE; + guint i; + + g_return_val_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + data = g_object_get_qdata (G_OBJECT (widget), quark_widget_controller_data); + + if (!data) + return FALSE; + + for (i = 0; i < data->controllers->len; i++) + { + item = &g_array_index (data->controllers, SugarControllerItem, i); + + if (item->controller == controller) + { + sugar_event_controller_reset (item->controller); + g_object_unref (item->controller); + g_signal_handler_disconnect (item->controller, + item->notify_handler_id); + + g_array_remove_index_fast (data->controllers, i); + removed = TRUE; + } + } + + if (data->controllers->len == 0) + g_object_set_qdata (G_OBJECT (widget), quark_widget_controller_data, NULL); + + return removed; +} + +gboolean +sugar_event_controller_reset (SugarEventController *controller) +{ + SugarEventControllerClass *controller_class; + + g_return_val_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller), FALSE); + + controller_class = SUGAR_EVENT_CONTROLLER_GET_CLASS (controller); + + if (!controller_class->reset) + return FALSE; + + controller_class->reset (controller); + + return sugar_event_controller_get_state (controller) == + SUGAR_EVENT_CONTROLLER_STATE_NONE; +} + +SugarEventControllerState +sugar_event_controller_get_state (SugarEventController *controller) +{ + SugarEventControllerClass *controller_class; + + g_return_val_if_fail (SUGAR_IS_EVENT_CONTROLLER (controller), + SUGAR_EVENT_CONTROLLER_STATE_NONE); + + controller_class = SUGAR_EVENT_CONTROLLER_GET_CLASS (controller); + + if (!controller_class->get_state) + return SUGAR_EVENT_CONTROLLER_STATE_NONE; + + return controller_class->get_state (controller); +} diff --git a/src/sugar3/event-controller/sugar-event-controller.h b/src/sugar3/event-controller/sugar-event-controller.h new file mode 100644 index 00000000..c96d76d2 --- /dev/null +++ b/src/sugar3/event-controller/sugar-event-controller.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_EVENT_CONTROLLER_H__ +#define __SUGAR_EVENT_CONTROLLER_H__ + +#include + +G_BEGIN_DECLS + +#define SUGAR_TYPE_EVENT_CONTROLLER (sugar_event_controller_get_type ()) +#define SUGAR_EVENT_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUGAR_TYPE_EVENT_CONTROLLER, SugarEventController)) +#define SUGAR_EVENT_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUGAR_TYPE_EVENT_CONTROLLER, SugarEventControllerClass)) +#define SUGAR_IS_EVENT_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUGAR_TYPE_EVENT_CONTROLLER)) +#define SUGAR_IS_EVENT_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUGAR_TYPE_EVENT_CONTROLLER)) +#define SUGAR_EVENT_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SUGAR_TYPE_EVENT_CONTROLLER, SugarEventControllerClass)) + +typedef struct _SugarEventController SugarEventController; +typedef struct _SugarEventControllerClass SugarEventControllerClass; + +typedef enum { + SUGAR_EVENT_CONTROLLER_STATE_NONE, + SUGAR_EVENT_CONTROLLER_STATE_COLLECTING, + SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED, + SUGAR_EVENT_CONTROLLER_STATE_NOT_RECOGNIZED +} SugarEventControllerState; + +typedef enum { + SUGAR_EVENT_CONTROLLER_FLAG_NONE = 0, + SUGAR_EVENT_CONTROLLER_FLAG_EXCLUSIVE = 1 << 0 +} SugarEventControllerFlags; + +struct _SugarEventController +{ + GObject parent_instance; + gpointer _priv; +}; + +struct _SugarEventControllerClass +{ + GObjectClass parent_class; + + /* Signals */ + void (* started) (SugarEventController *controller); + void (* finished) (SugarEventController *controller); + + /* vmethods */ + gboolean (* handle_event) (SugarEventController *controller, + GdkEvent *event); + SugarEventControllerState (* get_state) (SugarEventController *controller); + void (* reset) (SugarEventController *controller); +}; + +GType sugar_event_controller_get_type (void) G_GNUC_CONST; +gboolean sugar_event_controller_handle_event (SugarEventController *controller, + GdkEvent *event); +gboolean sugar_event_controller_attach (SugarEventController *controller, + GtkWidget *widget, + SugarEventControllerFlags flags); +gboolean sugar_event_controller_reset (SugarEventController *controller); + +SugarEventControllerState + sugar_event_controller_get_state (SugarEventController *controller); + +G_END_DECLS + +#endif /* __SUGAR_EVENT_CONTROLLER_H__ */ diff --git a/src/sugar3/event-controller/sugar-event-controllers.h b/src/sugar3/event-controller/sugar-event-controllers.h new file mode 100644 index 00000000..d043f78d --- /dev/null +++ b/src/sugar3/event-controller/sugar-event-controllers.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#ifndef __SUGAR_EVENT_CONTROLLERS_H__ +#define __SUGAR_EVENT_CONTROLLERS_H__ + +#define __SUGAR_CONTROLLERS_H_INSIDE__ + +#include "sugar-event-controller.h" +#include "sugar-long-press-controller.h" +#include "sugar-rotate-controller.h" +#include "sugar-zoom-controller.h" +#include "sugar-swipe-controller.h" + +#undef __SUGAR_CONTROLLERS_H_INSIDE__ + +#endif /* __SUGAR_EVENT_CONTROLLERS_H__ */ diff --git a/src/sugar3/event-controller/sugar-long-press-controller.c b/src/sugar3/event-controller/sugar-long-press-controller.c new file mode 100644 index 00000000..b68c1fb9 --- /dev/null +++ b/src/sugar3/event-controller/sugar-long-press-controller.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#include "sugar-long-press-controller.h" + +#define DEFAULT_THRESHOLD 32 +#define DEFAULT_TIMEOUT 800 + +typedef struct _SugarLongPressControllerPriv SugarLongPressControllerPriv; + +enum { + PROP_0, + PROP_THRESHOLD, + PROP_TIMEOUT +}; + +struct _SugarLongPressControllerPriv +{ + GdkDevice *device; + GdkEventSequence *sequence; + gint x; + gint y; + guint timeout_id; + guint threshold; + guint timeout; + guint cancelled : 1; + guint triggered : 1; +}; + +G_DEFINE_TYPE (SugarLongPressController, + sugar_long_press_controller, + SUGAR_TYPE_EVENT_CONTROLLER) + +static void +sugar_long_press_controller_init (SugarLongPressController *controller) +{ + SugarLongPressControllerPriv *priv; + + controller->_priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (controller, + SUGAR_TYPE_LONG_PRESS_CONTROLLER, + SugarLongPressControllerPriv); + priv->threshold = DEFAULT_THRESHOLD; + priv->timeout = DEFAULT_TIMEOUT; + priv->x = priv->y = -1; +} + +static void +_sugar_long_press_controller_unset_device (SugarLongPressController *controller) +{ + SugarLongPressControllerPriv *priv = controller->_priv; + + if (priv->device) + { + g_object_unref (priv->device); + priv->device = NULL; + } + + priv->sequence = NULL; + priv->x = priv->y = -1; + priv->cancelled = priv->triggered = FALSE; +} + +static gboolean +_sugar_long_press_controller_cancel (SugarLongPressController *controller) +{ + SugarLongPressControllerPriv *priv = controller->_priv; + + if (priv->timeout_id) + { + g_source_remove (priv->timeout_id); + priv->timeout_id = 0; + priv->cancelled = TRUE; + g_object_notify (G_OBJECT (controller), "state"); + + return TRUE; + } + + return FALSE; +} + +static void +sugar_long_press_controller_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SugarLongPressControllerPriv *priv = SUGAR_LONG_PRESS_CONTROLLER (object)->_priv; + + switch (prop_id) + { + case PROP_THRESHOLD: + g_value_set_uint (value, priv->threshold); + break; + case PROP_TIMEOUT: + g_value_set_uint (value, priv->timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +sugar_long_press_controller_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SugarLongPressControllerPriv *priv = SUGAR_LONG_PRESS_CONTROLLER (object)->_priv; + + switch (prop_id) + { + case PROP_THRESHOLD: + priv->threshold = g_value_get_uint (value); + break; + case PROP_TIMEOUT: + priv->timeout = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +sugar_long_press_controller_finalize (GObject *object) +{ + SugarLongPressController *controller = SUGAR_LONG_PRESS_CONTROLLER (object); + + _sugar_long_press_controller_cancel (controller); + _sugar_long_press_controller_unset_device (controller); + + G_OBJECT_CLASS (sugar_long_press_controller_parent_class)->finalize (object); +} + +static gboolean +_sugar_long_press_controller_timeout (gpointer user_data) +{ + SugarLongPressController *controller = user_data; + SugarLongPressControllerPriv *priv = controller->_priv; + + priv->timeout_id = 0; + priv->triggered = TRUE; + g_signal_emit_by_name (controller, "started"); + + return FALSE; +} + +static SugarEventControllerState +sugar_long_press_controller_get_state (SugarEventController *controller) +{ + SugarLongPressControllerPriv *priv; + + priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; + + if (priv->device) + { + if (priv->timeout_id) + return SUGAR_EVENT_CONTROLLER_STATE_COLLECTING; + else if (priv->cancelled) + return SUGAR_EVENT_CONTROLLER_STATE_NOT_RECOGNIZED; + else if (priv->triggered) + return SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED; + } + + return SUGAR_EVENT_CONTROLLER_STATE_NONE; +} + +static void +sugar_long_press_controller_reset (SugarEventController *controller) +{ + SugarLongPressControllerPriv *priv; + + priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; + + if (priv->triggered) + g_signal_emit_by_name (controller, "finished"); + + _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); + _sugar_long_press_controller_unset_device (SUGAR_LONG_PRESS_CONTROLLER (controller)); + g_object_notify (G_OBJECT (controller), "state"); +} + +static gboolean +sugar_long_press_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + SugarLongPressControllerPriv *priv; + GdkEventSequence *sequence; + gboolean handled = TRUE; + GdkDevice *device; + + priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->_priv; + device = gdk_event_get_device (event); + sequence = gdk_event_get_event_sequence (event); + + if (priv->device) + { + if (priv->device != device) + return FALSE; + + if (sequence && priv->sequence != sequence) + { + /* Another touch is simultaneously operating, + * give up on recognizing a long press. + */ + _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); + + return FALSE; + } + } + + switch (event->type) + { + case GDK_TOUCH_BEGIN: + priv->device = g_object_ref (device); + priv->x = event->touch.x; + priv->y = event->touch.y; + priv->sequence = sequence; + + priv->timeout_id = + gdk_threads_add_timeout (priv->timeout, + _sugar_long_press_controller_timeout, + controller); + g_object_notify (G_OBJECT (controller), "state"); + break; + case GDK_TOUCH_UPDATE: + if (ABS (priv->x - event->touch.x) > priv->threshold || + ABS (priv->y - event->touch.y) > priv->threshold) + _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); + break; + case GDK_TOUCH_END: + sugar_event_controller_reset (controller); + break; + default: + handled = FALSE; + break; + } + + return handled; +} + +static void +sugar_long_press_controller_class_init (SugarLongPressControllerClass *klass) +{ + SugarEventControllerClass *controller_class; + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = sugar_long_press_controller_get_property; + object_class->set_property = sugar_long_press_controller_set_property; + object_class->finalize = sugar_long_press_controller_finalize; + + controller_class = SUGAR_EVENT_CONTROLLER_CLASS (klass); + controller_class->handle_event = sugar_long_press_controller_handle_event; + controller_class->get_state = sugar_long_press_controller_get_state; + controller_class->reset = sugar_long_press_controller_reset; + + g_object_class_install_property (object_class, + PROP_THRESHOLD, + g_param_spec_uint ("threshold", + "Threshold", + "Threshold in pixels where the long " + "press operation remains valid", + 0, G_MAXUINT, DEFAULT_THRESHOLD, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + g_object_class_install_property (object_class, + PROP_TIMEOUT, + g_param_spec_uint ("timeout", + "Timeout", + "Value in milliseconds to timeout the triggering", + 0, G_MAXUINT, DEFAULT_TIMEOUT, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (klass, sizeof (SugarLongPressControllerPriv)); +} + +SugarEventController * +sugar_long_press_controller_new (void) +{ + return g_object_new (SUGAR_TYPE_LONG_PRESS_CONTROLLER, NULL); +} diff --git a/src/sugar3/event-controller/sugar-long-press-controller.h b/src/sugar3/event-controller/sugar-long-press-controller.h new file mode 100644 index 00000000..1715f0c9 --- /dev/null +++ b/src/sugar3/event-controller/sugar-long-press-controller.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_LONG_PRESS_CONTROLLER_H__ +#define __SUGAR_LONG_PRESS_CONTROLLER_H__ + +#include "sugar-event-controller.h" +#include + +G_BEGIN_DECLS + +#define SUGAR_TYPE_LONG_PRESS_CONTROLLER (sugar_long_press_controller_get_type ()) +#define SUGAR_LONG_PRESS_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUGAR_TYPE_LONG_PRESS_CONTROLLER, SugarLongPressController)) +#define SUGAR_LONG_PRESS_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUGAR_TYPE_LONG_PRESS_CONTROLLER, SugarLongPressControllerClass)) +#define SUGAR_IS_LONG_PRESS_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUGAR_TYPE_LONG_PRESS_CONTROLLER)) +#define SUGAR_IS_LONG_PRESS_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUGAR_TYPE_LONG_PRESS_CONTROLLER)) +#define SUGAR_LONG_PRESS_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SUGAR_TYPE_LONG_PRESS_CONTROLLER, SugarLongPressControllerClass)) + +typedef struct _SugarLongPressController SugarLongPressController; +typedef struct _SugarLongPressControllerClass SugarLongPressControllerClass; + +struct _SugarLongPressController +{ + SugarEventController parent_instance; + gpointer _priv; +}; + +struct _SugarLongPressControllerClass +{ + SugarEventControllerClass parent_class; +}; + +GType sugar_long_press_controller_get_type (void) G_GNUC_CONST; +SugarEventController * sugar_long_press_controller_new (void); + +G_END_DECLS + +#endif /* __SUGAR_LONG_PRESS_CONTROLLER_H__ */ diff --git a/src/sugar3/event-controller/sugar-rotate-controller.c b/src/sugar3/event-controller/sugar-rotate-controller.c new file mode 100644 index 00000000..34cb893c --- /dev/null +++ b/src/sugar3/event-controller/sugar-rotate-controller.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#include +#include "sugar-rotate-controller.h" + +typedef struct _SugarRotateControllerPriv SugarRotateControllerPriv; +typedef struct _SugarTouch SugarTouch; + +enum { + ANGLE_CHANGED, + LAST_SIGNAL +}; + +struct _SugarTouch +{ + GdkEventSequence *sequence; + gint x; + gint y; + guint set : 1; +}; + +struct _SugarRotateControllerPriv +{ + GdkDevice *device; + SugarTouch touches[2]; + gdouble initial_angle; +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (SugarRotateController, + sugar_rotate_controller, + SUGAR_TYPE_EVENT_CONTROLLER) + +static void +sugar_rotate_controller_init (SugarRotateController *controller) +{ + controller->_priv = G_TYPE_INSTANCE_GET_PRIVATE (controller, + SUGAR_TYPE_ROTATE_CONTROLLER, + SugarRotateControllerPriv); +} + +static void +sugar_rotate_controller_finalize (GObject *object) +{ + G_OBJECT_CLASS (sugar_rotate_controller_parent_class)->finalize (object); +} + +static SugarTouch * +_sugar_rotate_controller_find_touch (SugarRotateController *controller, + GdkEventSequence *sequence) +{ + SugarRotateControllerPriv *priv; + gint unset = -1, i; + + priv = controller->_priv; + + for (i = 0; i < 2; i++) + { + if (priv->touches[i].sequence == sequence) + return &priv->touches[i]; + else if (!priv->touches[i].set && unset < 0) + unset = i; + } + + if (unset < 0) + return NULL; + + priv->touches[unset].sequence = sequence; + priv->touches[unset].set = TRUE; + + return &priv->touches[unset]; +} + +static gboolean +_sugar_rotate_controller_get_angle (SugarRotateController *controller, + gdouble *angle) +{ + SugarRotateControllerPriv *priv; + gdouble dx, dy; + + priv = controller->_priv; + + if (!priv->touches[0].set || !priv->touches[1].set) + return FALSE; + + dx = priv->touches[0].x - priv->touches[1].x; + dy = priv->touches[0].y - priv->touches[1].y; + + *angle = atan2 (dx, dy); + + /* Invert angle */ + *angle = (2 * G_PI) - *angle; + + /* And constraint it to 0°-360° */ + *angle = fmod (*angle, 2 * G_PI); + + return TRUE; +} + +static gboolean +_sugar_rotate_controller_check_emit (SugarRotateController *controller) +{ + SugarRotateControllerPriv *priv; + gdouble angle; + + if (!_sugar_rotate_controller_get_angle (controller, &angle)) + return FALSE; + + priv = controller->_priv; + + g_signal_emit (controller, signals[ANGLE_CHANGED], 0, + angle, angle - priv->initial_angle); + return TRUE; +} + +static gboolean +sugar_rotate_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + SugarRotateControllerPriv *priv; + GdkEventSequence *sequence; + gboolean handled = TRUE; + GdkDevice *device; + SugarTouch *touch; + + priv = SUGAR_ROTATE_CONTROLLER (controller)->_priv; + device = gdk_event_get_device (event); + sequence = gdk_event_get_event_sequence (event); + + if (priv->device && priv->device != device) + return FALSE; + + touch = _sugar_rotate_controller_find_touch (SUGAR_ROTATE_CONTROLLER (controller), + sequence); + if (!touch) + return FALSE; + + switch (event->type) + { + case GDK_TOUCH_BEGIN: + touch->x = event->touch.x; + touch->y = event->touch.y; + + if (!priv->device) + priv->device = g_object_ref (device); + + if (priv->touches[0].set && priv->touches[1].set) + { + _sugar_rotate_controller_get_angle (SUGAR_ROTATE_CONTROLLER (controller), + &priv->initial_angle); + g_signal_emit_by_name (G_OBJECT (controller), "started"); + g_object_notify (G_OBJECT (controller), "state"); + } + break; + case GDK_TOUCH_END: + touch->sequence = NULL; + touch->set = FALSE; + + if (!priv->touches[0].set && !priv->touches[1].set) + { + g_object_unref (priv->device); + priv->device = NULL; + } + else if (priv->touches[0].set || priv->touches[1].set) + { + g_signal_emit_by_name (G_OBJECT (controller), "finished"); + g_object_notify (G_OBJECT (controller), "state"); + } + break; + case GDK_TOUCH_UPDATE: + touch->x = event->touch.x; + touch->y = event->touch.y; + _sugar_rotate_controller_check_emit (SUGAR_ROTATE_CONTROLLER (controller)); + break; + default: + handled = FALSE; + break; + } + + return handled; +} + +SugarEventControllerState +sugar_rotate_controller_get_state (SugarEventController *controller) +{ + SugarRotateControllerPriv *priv; + + priv = SUGAR_ROTATE_CONTROLLER (controller)->_priv; + + if (priv->device) + { + if (priv->touches[0].set && priv->touches[1].set) + return SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED; + else if (priv->touches[0].set || priv->touches[1].set) + return SUGAR_EVENT_CONTROLLER_STATE_COLLECTING; + } + + return SUGAR_EVENT_CONTROLLER_STATE_NONE; +} + +void +sugar_rotate_controller_reset (SugarEventController *controller) +{ + SugarRotateControllerPriv *priv; + + priv = SUGAR_ROTATE_CONTROLLER (controller)->_priv; + + if (priv->touches[0].set && priv->touches[1].set) + g_signal_emit_by_name (G_OBJECT (controller), "finished"); + + priv->touches[0].sequence = NULL; + priv->touches[0].set = FALSE; + priv->touches[1].sequence = NULL; + priv->touches[1].set = FALSE; + + if (priv->device) + { + g_object_unref (priv->device); + priv->device = NULL; + } + + g_object_notify (G_OBJECT (controller), "state"); +} + +static void +sugar_rotate_controller_class_init (SugarRotateControllerClass *klass) +{ + SugarEventControllerClass *controller_class; + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = sugar_rotate_controller_finalize; + + controller_class = SUGAR_EVENT_CONTROLLER_CLASS (klass); + controller_class->handle_event = sugar_rotate_controller_handle_event; + controller_class->get_state = sugar_rotate_controller_get_state; + controller_class->reset = sugar_rotate_controller_reset; + + signals[ANGLE_CHANGED] = + g_signal_new ("angle-changed", + SUGAR_TYPE_ROTATE_CONTROLLER, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SugarRotateControllerClass, angle_changed), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 2, + G_TYPE_DOUBLE, G_TYPE_DOUBLE); + + g_type_class_add_private (klass, sizeof (SugarRotateControllerPriv)); +} + +SugarEventController * +sugar_rotate_controller_new (void) +{ + return g_object_new (SUGAR_TYPE_ROTATE_CONTROLLER, NULL); +} + +gboolean +sugar_rotate_controller_get_angle_delta (SugarRotateController *controller, + gdouble *delta) +{ + SugarRotateControllerPriv *priv; + gdouble angle; + + g_return_val_if_fail (SUGAR_IS_ROTATE_CONTROLLER (controller), FALSE); + + if (!_sugar_rotate_controller_get_angle (controller, &angle)) + return FALSE; + + priv = controller->_priv; + + if (delta) + *delta = angle - priv->initial_angle; + + return TRUE; +} diff --git a/src/sugar3/event-controller/sugar-rotate-controller.h b/src/sugar3/event-controller/sugar-rotate-controller.h new file mode 100644 index 00000000..521bd2db --- /dev/null +++ b/src/sugar3/event-controller/sugar-rotate-controller.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_ROTATE_CONTROLLER_H__ +#define __SUGAR_ROTATE_CONTROLLER_H__ + +#include "sugar-event-controller.h" +#include + +G_BEGIN_DECLS + +#define SUGAR_TYPE_ROTATE_CONTROLLER (sugar_rotate_controller_get_type ()) +#define SUGAR_ROTATE_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUGAR_TYPE_ROTATE_CONTROLLER, SugarRotateController)) +#define SUGAR_ROTATE_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUGAR_TYPE_ROTATE_CONTROLLER, SugarRotateControllerClass)) +#define SUGAR_IS_ROTATE_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUGAR_TYPE_ROTATE_CONTROLLER)) +#define SUGAR_IS_ROTATE_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUGAR_TYPE_ROTATE_CONTROLLER)) +#define SUGAR_ROTATE_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SUGAR_TYPE_ROTATE_CONTROLLER, SugarRotateControllerClass)) + +typedef struct _SugarRotateController SugarRotateController; +typedef struct _SugarRotateControllerClass SugarRotateControllerClass; + +struct _SugarRotateController +{ + SugarEventController parent_instance; + gpointer _priv; +}; + +struct _SugarRotateControllerClass +{ + SugarEventControllerClass parent_class; + + void (* angle_changed) (SugarRotateController *controller, + gdouble angle, + gdouble delta); +}; + +GType sugar_rotate_controller_get_type (void) G_GNUC_CONST; +SugarEventController * sugar_rotate_controller_new (void); + +gboolean sugar_rotate_controller_get_angle_delta (SugarRotateController *controller, + gdouble *delta); + + +G_END_DECLS + +#endif /* __SUGAR_ROTATE_CONTROLLER_H__ */ diff --git a/src/sugar3/event-controller/sugar-swipe-controller.c b/src/sugar3/event-controller/sugar-swipe-controller.c new file mode 100644 index 00000000..2d166892 --- /dev/null +++ b/src/sugar3/event-controller/sugar-swipe-controller.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#include "sugar-swipe-controller.h" +#include "sugar-enum-types.h" + +#define CHECK_TIME 100 +#define SWIPE_PX_THRESHOLD 80 +#define PROPORTION_FACTOR_THRESHOLD 4 + +typedef struct _SugarSwipeControllerPriv SugarSwipeControllerPriv; +typedef struct _SugarEventData SugarEventData; + +enum { + SWIPE_FINISHED, + LAST_SIGNAL +}; + +struct _SugarEventData +{ + gint x; + gint y; + guint32 time; +}; + +struct _SugarSwipeControllerPriv +{ + GdkDevice *device; + GdkEventSequence *sequence; + GArray *event_data; + guint swiped : 1; +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (SugarSwipeController, + sugar_swipe_controller, + SUGAR_TYPE_EVENT_CONTROLLER) + +static void +sugar_swipe_controller_init (SugarSwipeController *controller) +{ + SugarSwipeControllerPriv *priv; + + controller->_priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (controller, + SUGAR_TYPE_SWIPE_CONTROLLER, + SugarSwipeControllerPriv); + priv->event_data = g_array_new (FALSE, FALSE, sizeof (SugarEventData)); +} + +static void +sugar_swipe_controller_finalize (GObject *object) +{ + G_OBJECT_CLASS (sugar_swipe_controller_parent_class)->finalize (object); +} + +static void +_sugar_swipe_controller_clear_events (SugarSwipeController *controller) +{ + SugarSwipeControllerPriv *priv; + + priv = controller->_priv; + + if (priv->event_data && + priv->event_data->len > 0) + g_array_remove_range (priv->event_data, 0, priv->event_data->len); + + priv->swiped = FALSE; +} + +static void +_sugar_swipe_controller_store_event (SugarSwipeController *controller, + GdkEvent *event) +{ + SugarSwipeControllerPriv *priv; + SugarEventData data; + gdouble x, y; + guint32 time; + guint i; + + priv = controller->_priv; + + if (!gdk_event_get_coords (event, &x, &y)) + return; + + time = gdk_event_get_time (event); + + /* Remove event data older than CHECK_TIME, won't + * be used for calculations. + */ + for (i = 0; i < priv->event_data->len; i++) + { + SugarEventData *ptr; + + ptr = &g_array_index (priv->event_data, SugarEventData, i); + + if (ptr->time > time - CHECK_TIME) + break; + } + + if (i > 0) + g_array_remove_range (priv->event_data, 0, i); + + + /* And insert current event data */ + data.x = x; + data.y = y; + data.time = time; + g_array_append_val (priv->event_data, data); +} + +static gboolean +_sugar_swipe_controller_get_direction (SugarEventData *from, + SugarEventData *to, + SugarSwipeDirection *direction) +{ + gdouble dx, dy; + + if (!from || !to) + return FALSE; + + dx = to->x - from->x; + dy = to->y - from->y; + + if (ABS (dx) > SWIPE_PX_THRESHOLD && + ABS (dx) > ABS (dy) * PROPORTION_FACTOR_THRESHOLD) + { + if (dx < 0) + *direction = SUGAR_SWIPE_DIRECTION_LEFT; + else + *direction = SUGAR_SWIPE_DIRECTION_RIGHT; + + return TRUE; + } + else if (ABS (dy) > SWIPE_PX_THRESHOLD && + ABS (dy) > ABS (dx) * PROPORTION_FACTOR_THRESHOLD) + { + if (dy < 0) + *direction = SUGAR_SWIPE_DIRECTION_UP; + else + *direction = SUGAR_SWIPE_DIRECTION_DOWN; + + return TRUE; + } + + return FALSE; +} + +static void +_sugar_swipe_controller_check_emit (SugarSwipeController *controller) +{ + SugarSwipeControllerPriv *priv; + SugarEventData *last, *check; + SugarSwipeDirection direction; + gint i; + + priv = controller->_priv; + + if (!priv->event_data || priv->event_data->len == 0) + return; + + last = &g_array_index (priv->event_data, SugarEventData, + priv->event_data->len - 1); + + for (i = priv->event_data->len - 1; i >= 0; i--) + { + check = &g_array_index (priv->event_data, SugarEventData, i); + + if (check->time < last->time - CHECK_TIME) + break; + } + + if (_sugar_swipe_controller_get_direction (check, last, &direction)) + { + priv->swiped = TRUE; + g_signal_emit_by_name (G_OBJECT (controller), "started"); + g_object_notify (G_OBJECT (controller), "state"); + g_signal_emit (controller, signals[SWIPE_FINISHED], 0, direction); + g_signal_emit_by_name (G_OBJECT (controller), "finished"); + } +} + +static gboolean +sugar_swipe_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + SugarSwipeControllerPriv *priv; + SugarSwipeController *swipe; + GdkEventSequence *sequence; + gboolean handled = TRUE; + GdkDevice *device; + + device = gdk_event_get_device (event); + sequence = gdk_event_get_event_sequence (event); + + if (!device || !sequence) + return FALSE; + + swipe = SUGAR_SWIPE_CONTROLLER (controller); + priv = swipe->_priv; + + if ((priv->device && priv->device != device) || + (priv->sequence && priv->sequence != sequence)) + return FALSE; + + switch (event->type) + { + case GDK_TOUCH_BEGIN: + priv->device = g_object_ref (device); + priv->sequence = sequence; + _sugar_swipe_controller_clear_events (swipe); + _sugar_swipe_controller_store_event (swipe, event); + g_object_notify (G_OBJECT (controller), "state"); + break; + case GDK_TOUCH_END: + if (priv->device) + g_object_unref (priv->device); + priv->device = NULL; + priv->sequence = NULL; + _sugar_swipe_controller_store_event (swipe, event); + _sugar_swipe_controller_check_emit (swipe); + _sugar_swipe_controller_clear_events (swipe); + g_object_notify (G_OBJECT (controller), "state"); + break; + case GDK_TOUCH_UPDATE: + _sugar_swipe_controller_store_event (swipe, event); + break; + default: + handled = FALSE; + break; + } + + return handled; +} + +SugarEventControllerState +sugar_swipe_controller_get_state (SugarEventController *controller) +{ + SugarSwipeControllerPriv *priv; + + priv = SUGAR_SWIPE_CONTROLLER (controller)->_priv; + + if (priv->device) + { + if (priv->swiped) + return SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED; + else if (priv->event_data->len > 0) + return SUGAR_EVENT_CONTROLLER_STATE_COLLECTING; + } + + return SUGAR_EVENT_CONTROLLER_STATE_NONE; +} + +void +sugar_swipe_controller_reset (SugarEventController *controller) +{ + SugarSwipeControllerPriv *priv; + SugarSwipeController *swipe; + + swipe = SUGAR_SWIPE_CONTROLLER (controller); + priv = swipe->_priv; + + if (priv->device) + { + g_object_unref (priv->device); + priv->device = NULL; + } + + _sugar_swipe_controller_clear_events (swipe); + g_object_notify (G_OBJECT (controller), "state"); +} + +static void +sugar_swipe_controller_class_init (SugarSwipeControllerClass *klass) +{ + SugarEventControllerClass *controller_class; + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = sugar_swipe_controller_finalize; + + controller_class = SUGAR_EVENT_CONTROLLER_CLASS (klass); + controller_class->handle_event = sugar_swipe_controller_handle_event; + controller_class->get_state = sugar_swipe_controller_get_state; + controller_class->reset = sugar_swipe_controller_reset; + + signals[SWIPE_FINISHED] = + g_signal_new ("swipe-finished", + SUGAR_TYPE_SWIPE_CONTROLLER, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SugarSwipeControllerClass, swipe_finished), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + SUGAR_TYPE_SWIPE_DIRECTION); + + g_type_class_add_private (klass, sizeof (SugarSwipeControllerPriv)); +} + +SugarEventController * +sugar_swipe_controller_new (void) +{ + return g_object_new (SUGAR_TYPE_SWIPE_CONTROLLER, NULL); +} diff --git a/src/sugar3/event-controller/sugar-swipe-controller.h b/src/sugar3/event-controller/sugar-swipe-controller.h new file mode 100644 index 00000000..fdfa1cf0 --- /dev/null +++ b/src/sugar3/event-controller/sugar-swipe-controller.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_SWIPE_CONTROLLER_H__ +#define __SUGAR_SWIPE_CONTROLLER_H__ + +#include "sugar-event-controller.h" +#include + +G_BEGIN_DECLS + +#define SUGAR_TYPE_SWIPE_CONTROLLER (sugar_swipe_controller_get_type ()) +#define SUGAR_SWIPE_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUGAR_TYPE_SWIPE_CONTROLLER, SugarSwipeController)) +#define SUGAR_SWIPE_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUGAR_TYPE_SWIPE_CONTROLLER, SugarSwipeControllerClass)) +#define SUGAR_IS_SWIPE_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUGAR_TYPE_SWIPE_CONTROLLER)) +#define SUGAR_IS_SWIPE_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUGAR_TYPE_SWIPE_CONTROLLER)) +#define SUGAR_SWIPE_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SUGAR_TYPE_SWIPE_CONTROLLER, SugarSwipeControllerClass)) + +typedef struct _SugarSwipeController SugarSwipeController; +typedef struct _SugarSwipeControllerClass SugarSwipeControllerClass; + +typedef enum { + SUGAR_SWIPE_DIRECTION_LEFT, + SUGAR_SWIPE_DIRECTION_RIGHT, + SUGAR_SWIPE_DIRECTION_UP, + SUGAR_SWIPE_DIRECTION_DOWN +} SugarSwipeDirection; + +struct _SugarSwipeController +{ + SugarEventController parent_instance; + gpointer _priv; +}; + +struct _SugarSwipeControllerClass +{ + SugarEventControllerClass parent_class; + + void (* swipe_finished) (SugarSwipeController *controller, + SugarSwipeDirection direction); +}; + +GType sugar_swipe_controller_get_type (void) G_GNUC_CONST; +SugarEventController * sugar_swipe_controller_new (void); + +G_END_DECLS + +#endif /* __SUGAR_SWIPE_CONTROLLER_H__ */ diff --git a/src/sugar3/event-controller/sugar-zoom-controller.c b/src/sugar3/event-controller/sugar-zoom-controller.c new file mode 100644 index 00000000..48211478 --- /dev/null +++ b/src/sugar3/event-controller/sugar-zoom-controller.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#include +#include "sugar-zoom-controller.h" + +typedef struct _SugarZoomControllerPriv SugarZoomControllerPriv; +typedef struct _SugarTouch SugarTouch; + +enum { + ZOOM_CHANGED, + LAST_SIGNAL +}; + +struct _SugarTouch +{ + GdkEventSequence *sequence; + gint x; + gint y; + guint set : 1; +}; + +struct _SugarZoomControllerPriv +{ + GdkDevice *device; + SugarTouch touches[2]; + gdouble initial_distance; +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (SugarZoomController, + sugar_zoom_controller, + SUGAR_TYPE_EVENT_CONTROLLER) + +static void +sugar_zoom_controller_init (SugarZoomController *controller) +{ + controller->_priv = G_TYPE_INSTANCE_GET_PRIVATE (controller, + SUGAR_TYPE_ZOOM_CONTROLLER, + SugarZoomControllerPriv); +} + +static void +sugar_zoom_controller_finalize (GObject *object) +{ + G_OBJECT_CLASS (sugar_zoom_controller_parent_class)->finalize (object); +} + +static SugarTouch * +_sugar_zoom_controller_find_touch (SugarZoomController *controller, + GdkEventSequence *sequence) +{ + SugarZoomControllerPriv *priv; + gint unset = -1, i; + + priv = controller->_priv; + + for (i = 0; i < 2; i++) + { + if (priv->touches[i].sequence == sequence) + return &priv->touches[i]; + else if (!priv->touches[i].set && unset < 0) + unset = i; + } + + if (unset < 0) + return NULL; + + priv->touches[unset].sequence = sequence; + priv->touches[unset].set = TRUE; + return &priv->touches[unset]; +} + +static gboolean +_sugar_zoom_controller_get_distance (SugarZoomController *controller, + gdouble *distance) +{ + SugarZoomControllerPriv *priv; + gdouble dx, dy; + + priv = controller->_priv; + + if (!priv->touches[0].set || !priv->touches[1].set) + return FALSE; + + dx = priv->touches[0].x - priv->touches[1].x; + dy = priv->touches[0].y - priv->touches[1].y; + *distance = sqrt ((dx * dx) + (dy * dy)); + + return TRUE; +} + +static gboolean +_sugar_zoom_controller_check_emit (SugarZoomController *controller) +{ + SugarZoomControllerPriv *priv; + gdouble distance, zoom; + + if (!_sugar_zoom_controller_get_distance (controller, &distance)) + return FALSE; + + priv = controller->_priv; + + if (distance == 0 || priv->initial_distance == 0) + return FALSE; + + zoom = distance / priv->initial_distance; + g_signal_emit (controller, signals[ZOOM_CHANGED], 0, zoom); + + return TRUE; +} + +static gboolean +sugar_zoom_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + SugarZoomControllerPriv *priv; + GdkEventSequence *sequence; + gboolean handled = TRUE; + GdkDevice *device; + SugarTouch *touch; + + priv = SUGAR_ZOOM_CONTROLLER (controller)->_priv; + device = gdk_event_get_device (event); + sequence = gdk_event_get_event_sequence (event); + + if (priv->device && priv->device != device) + return FALSE; + + touch = _sugar_zoom_controller_find_touch (SUGAR_ZOOM_CONTROLLER (controller), + sequence); + if (!touch) + return FALSE; + + switch (event->type) + { + case GDK_TOUCH_BEGIN: + touch->x = event->touch.x; + touch->y = event->touch.y; + + if (!priv->device) + priv->device = g_object_ref (device); + + if (priv->touches[0].set && priv->touches[1].set) + { + _sugar_zoom_controller_get_distance (SUGAR_ZOOM_CONTROLLER (controller), + &priv->initial_distance); + g_signal_emit_by_name (G_OBJECT (controller), "started"); + g_object_notify (G_OBJECT (controller), "state"); + } + break; + case GDK_TOUCH_END: + touch->sequence = NULL; + touch->set = FALSE; + + if (!priv->touches[0].set && !priv->touches[1].set) + { + g_object_unref (priv->device); + priv->device = NULL; + } + else if (!priv->touches[0].set || priv->touches[1].set) + { + g_signal_emit_by_name (G_OBJECT (controller), "finished"); + g_object_notify (G_OBJECT (controller), "state"); + } + break; + case GDK_TOUCH_UPDATE: + touch->x = event->touch.x; + touch->y = event->touch.y; + _sugar_zoom_controller_check_emit (SUGAR_ZOOM_CONTROLLER (controller)); + break; + default: + handled = FALSE; + break; + } + + return handled; +} + +SugarEventControllerState +sugar_zoom_controller_get_state (SugarEventController *controller) +{ + SugarZoomControllerPriv *priv; + + priv = SUGAR_ZOOM_CONTROLLER (controller)->_priv; + + if (priv->device) + { + if (priv->touches[0].set && priv->touches[1].set) + return SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED; + else if (priv->touches[0].set || priv->touches[1].set) + return SUGAR_EVENT_CONTROLLER_STATE_COLLECTING; + } + + return SUGAR_EVENT_CONTROLLER_STATE_NONE; +} + +void +sugar_zoom_controller_reset (SugarEventController *controller) +{ + SugarZoomControllerPriv *priv; + + priv = SUGAR_ZOOM_CONTROLLER (controller)->_priv; + + if (priv->touches[0].set && priv->touches[1].set) + g_signal_emit_by_name (G_OBJECT (controller), "finished"); + + priv->touches[0].sequence = NULL; + priv->touches[0].set = FALSE; + priv->touches[1].sequence = NULL; + priv->touches[1].set = FALSE; + + if (priv->device) + { + g_object_unref (priv->device); + priv->device = NULL; + } + + g_object_notify (G_OBJECT (controller), "state"); +} + +static void +sugar_zoom_controller_class_init (SugarZoomControllerClass *klass) +{ + SugarEventControllerClass *controller_class; + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = sugar_zoom_controller_finalize; + + controller_class = SUGAR_EVENT_CONTROLLER_CLASS (klass); + controller_class->handle_event = sugar_zoom_controller_handle_event; + controller_class->get_state = sugar_zoom_controller_get_state; + controller_class->reset = sugar_zoom_controller_reset; + + signals[ZOOM_CHANGED] = + g_signal_new ("zoom-changed", + SUGAR_TYPE_ZOOM_CONTROLLER, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SugarZoomControllerClass, zoom_changed), + NULL, NULL, + g_cclosure_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + g_type_class_add_private (klass, sizeof (SugarZoomControllerPriv)); +} + +SugarEventController * +sugar_zoom_controller_new (void) +{ + return g_object_new (SUGAR_TYPE_ZOOM_CONTROLLER, NULL); +} + +gboolean +sugar_zoom_controller_get_zoom_delta (SugarZoomController *controller, + gdouble *delta) +{ + SugarZoomControllerPriv *priv; + gdouble distance; + + g_return_val_if_fail (SUGAR_IS_ZOOM_CONTROLLER (controller), FALSE); + + if (!_sugar_zoom_controller_get_distance (controller, &distance)) + return FALSE; + + priv = controller->_priv; + + if (delta) + *delta = distance / priv->initial_distance; + + return TRUE; +} diff --git a/src/sugar3/event-controller/sugar-zoom-controller.h b/src/sugar3/event-controller/sugar-zoom-controller.h new file mode 100644 index 00000000..61977669 --- /dev/null +++ b/src/sugar3/event-controller/sugar-zoom-controller.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012, One Laptop Per Child. + * + * 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. + * + * Author(s): Carlos Garnacho + */ + +#if !defined (__SUGAR_CONTROLLERS_H_INSIDE__) && !defined (SUGAR_TOOLKIT_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __SUGAR_ZOOM_CONTROLLER_H__ +#define __SUGAR_ZOOM_CONTROLLER_H__ + +#include "sugar-event-controller.h" +#include + +G_BEGIN_DECLS + +#define SUGAR_TYPE_ZOOM_CONTROLLER (sugar_zoom_controller_get_type ()) +#define SUGAR_ZOOM_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SUGAR_TYPE_ZOOM_CONTROLLER, SugarZoomController)) +#define SUGAR_ZOOM_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), SUGAR_TYPE_ZOOM_CONTROLLER, SugarZoomControllerClass)) +#define SUGAR_IS_ZOOM_CONTROLLER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SUGAR_TYPE_ZOOM_CONTROLLER)) +#define SUGAR_IS_ZOOM_CONTROLLER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SUGAR_TYPE_ZOOM_CONTROLLER)) +#define SUGAR_ZOOM_CONTROLLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SUGAR_TYPE_ZOOM_CONTROLLER, SugarZoomControllerClass)) + +typedef struct _SugarZoomController SugarZoomController; +typedef struct _SugarZoomControllerClass SugarZoomControllerClass; + +struct _SugarZoomController +{ + SugarEventController parent_instance; + gpointer _priv; +}; + +struct _SugarZoomControllerClass +{ + SugarEventControllerClass parent_class; + + void (* zoom_changed) (SugarZoomController *controller, + gdouble zoom); +}; + +GType sugar_zoom_controller_get_type (void) G_GNUC_CONST; +SugarEventController * sugar_zoom_controller_new (void); +gboolean sugar_zoom_controller_get_zoom_delta (SugarZoomController *controller, + gdouble *delta); + +G_END_DECLS + +#endif /* __SUGAR_ZOOM_CONTROLLER_H__ */