[cheese] widget: turn the widget into a basic state machine



commit 3dbd5ff54ee7b3031462cc767ac5b174eb9aab93
Author: Filippo Argiolas <filippo argiolas gmail com>
Date:   Sun Feb 21 21:11:56 2010 +0100

    widget: turn the widget into a basic state machine
    
    Get rid of "ready" and "error" signals and use a "state" property to keep
    track of the current widget state (none, ready, error).
    Users can now connect to the notify::state signal to get notified about
    state changes.
    If an error occurred (i.e. the widget is in the error state) users can
    call cheese_widget_get_error() to get a GError containing more details
    about what went wrong.

 Makefile.am.enums                |   43 ++++++++++++++
 configure.ac                     |    2 +
 docs/reference/Makefile.am       |    1 +
 libcheese/Makefile.am            |    5 ++
 libcheese/cheese-enum-types.c.in |   40 +++++++++++++
 libcheese/cheese-enum-types.h.in |   26 ++++++++
 libcheese/cheese-gtk.symbols     |    3 +
 libcheese/cheese-widget.c        |  118 +++++++++++++++++++-------------------
 libcheese/cheese-widget.h        |   21 ++++++-
 9 files changed, 198 insertions(+), 61 deletions(-)
---
diff --git a/Makefile.am.enums b/Makefile.am.enums
new file mode 100644
index 0000000..6948509
--- /dev/null
+++ b/Makefile.am.enums
@@ -0,0 +1,43 @@
+# Rules for generating enumeration types using glib-mkenums
+#
+# Define:
+# 	glib_enum_h = header template file
+# 	glib_enum_c = source template file
+# 	glib_enum_headers = list of headers to parse
+#
+# before including Makefile.am.enums. You will also need to have
+# the following targets already defined:
+#
+# 	CLEANFILES
+#	DISTCLEANFILES
+#	BUILT_SOURCES
+#	EXTRA_DIST
+#
+# Author: Emmanuele Bassi <ebassi linux intel com>
+
+enum_tmpl_h=$(glib_enum_h:.h=.h.in)
+enum_tmpl_c=$(glib_enum_c:.c=.c.in)
+
+CLEANFILES += stamp-enum-types
+DISTCLEANFILES += $(glib_enum_h) $(glib_enum_c)
+BUILT_SOURCES += $(glib_enum_h) $(glib_enum_c)
+EXTRA_DIST += $(srcdir)/$(enum_tmpl_h) $(srcdir)/$(enum_tmpl_c)
+
+stamp-enum-types: $(glib_enum_headers) $(srcdir)/$(enum_tmpl_h)
+	$(AM_V_GEN) $(GLIB_MKENUMS) \
+		--template $(srcdir)/$(enum_tmpl_h) \
+	$(glib_enum_headers) > xgen-eh \
+	&& (cmp -s xgen-eh $(glib_enum_h) || cp -f xgen-eh $(glib_enum_h)) \
+	&& rm -f xgen-eh \
+	&& echo timestamp > $(@F)
+
+$(glib_enum_h): stamp-enum-types
+	@true
+
+$(glib_enum_c): $(glib_enum_h) $(srcdir)/$(enum_tmpl_c)
+	$(AM_V_GEN) $(GLIB_MKENUMS) \
+		--template $(srcdir)/$(enum_tmpl_c) \
+	$(glib_enum_headers) > xgen-ec \
+	&& cp -f xgen-ec $(glib_enum_c) \
+	&& rm -f xgen-ec
+
diff --git a/configure.ac b/configure.ac
index 6e5f8a7..c93ac0e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -148,7 +148,9 @@ CHEESE_LIBS="$CHEESE_LIBS -lgstinterfaces-0.10"
 AC_SUBST(CHEESE_LIBS)
 
 GLIB_GENMARSHAL=`$PKG_CONFIG --variable=glib_genmarshal glib-2.0`
+GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
 AC_SUBST(GLIB_GENMARSHAL)
+AC_SUBST(GLIB_MKENUMS)
 
 AC_PATH_PROG(GCONFTOOL, gconftool-2)
 AM_GCONF_SOURCE_2
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 93e3dce..7b14792 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -58,6 +58,7 @@ IGNORE_HFILES=					\
 	cheese-flash.h				\
 	cheese-gconf.h				\
 	cheese-widget-private.h			\
+	cheese-enum-types.h                     \
 	um-crop-area.h
 
 # Images to copy into HTML directory.
diff --git a/libcheese/Makefile.am b/libcheese/Makefile.am
index 2e652b9..e02387b 100644
--- a/libcheese/Makefile.am
+++ b/libcheese/Makefile.am
@@ -51,6 +51,7 @@ libcheese_gtk_la_SOURCES = \
 
 EXTRA_DIST = cheese-gtk.symbols cheese-marshal.list
 CLEANFILES = $(BUILT_SOURCES)
+DISTCLEANFILES =
 
 # FIXME when we have a .pc file, and sonames
 cheesedir = $(includedir)/cheese
@@ -65,3 +66,7 @@ libcheese_gtk_la_LDFLAGS = \
 	-no-undefined					\
 	$(AM_LDFLAGS)
 
+glib_enum_h = cheese-enum-types.h
+glib_enum_c = cheese-enum-types.c
+glib_enum_headers = cheese-widget.h
+include $(top_srcdir)/Makefile.am.enums
diff --git a/libcheese/cheese-enum-types.c.in b/libcheese/cheese-enum-types.c.in
new file mode 100644
index 0000000..622f8c5
--- /dev/null
+++ b/libcheese/cheese-enum-types.c.in
@@ -0,0 +1,40 @@
+/*** BEGIN file-header ***/
+#include "cheese-enum-types.h"
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+ enum_name@_get_type (void)
+{
+  static volatile gsize g_enum_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_enum_type_id__volatile))
+    {
+      static const G Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+        { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+        { 0, NULL, NULL }
+      };
+      GType g_enum_type_id;
+
+      g_enum_type_id =
+        g_ type@_register_static (g_intern_static_string ("@EnumName@"), values);
+
+      g_once_init_leave (&g_enum_type_id__volatile, g_enum_type_id);
+    }
+
+  return g_enum_type_id__volatile;
+}
+/*** END value-tail ***/
diff --git a/libcheese/cheese-enum-types.h.in b/libcheese/cheese-enum-types.h.in
new file mode 100644
index 0000000..a882619
--- /dev/null
+++ b/libcheese/cheese-enum-types.h.in
@@ -0,0 +1,26 @@
+/*** BEGIN file-header ***/
+#ifndef __CHEESE_ENUM_TYPES_H__
+#define __CHEESE_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* !__CHEESE_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name _get_type (void) G_GNUC_CONST;
+#define CHEESE_TYPE_ ENUMSHORT@ (@enum_name _get_type())
+
+/*** END value-header ***/
+
diff --git a/libcheese/cheese-gtk.symbols b/libcheese/cheese-gtk.symbols
index 4c26468..bd8dcd9 100644
--- a/libcheese/cheese-gtk.symbols
+++ b/libcheese/cheese-gtk.symbols
@@ -3,8 +3,11 @@ cheese_widget_new
 cheese_widget_get_camera
 cheese_widget_get_gconf
 cheese_widget_get_video_area
+cheese_widget_get_error
+cheese_widget_state_get_type
 cheese_gconf_get_type
 cheese_camera_get_type
+cheese_camera_cat
 cheese_camera_device_get_type
 cheese_camera_device_cat
 cheese_video_format_get_type
diff --git a/libcheese/cheese-widget.c b/libcheese/cheese-widget.c
index 2841cce..79a8d2b 100644
--- a/libcheese/cheese-widget.c
+++ b/libcheese/cheese-widget.c
@@ -24,6 +24,7 @@
 #include "cheese-widget.h"
 #include "cheese-gconf.h"
 #include "cheese-camera.h"
+#include "cheese-enum-types.h"
 
 enum
 {
@@ -35,7 +36,7 @@ enum
 enum
 {
   PROP_0,
-  PROP_WIDGET
+  PROP_STATE
 };
 
 enum
@@ -45,8 +46,6 @@ enum
   PROBLEM_PAGE = 2,
 };
 
-static guint widget_signals[LAST_SIGNAL] = {0};
-
 typedef struct
 {
   GtkWidget *spinner;
@@ -54,6 +53,8 @@ typedef struct
   GtkWidget *problem;
   CheeseGConf *gconf;
   CheeseCamera *webcam;
+  CheeseWidgetState state;
+  GError *error;
 } CheeseWidgetPrivate;
 
 #define CHEESE_WIDGET_GET_PRIVATE(o)                     \
@@ -185,6 +186,9 @@ cheese_widget_init (CheeseWidget *widget)
   CheeseWidgetPrivate *priv = CHEESE_WIDGET_GET_PRIVATE (widget);
   GtkWidget           *box;
 
+  priv->state = CHEESE_WIDGET_STATE_NONE;
+  priv->error = NULL;
+
   /* XXX
    * remove this line if you want to debug */
   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
@@ -247,14 +251,15 @@ cheese_widget_set_property (GObject *object, guint prop_id,
 
   switch (prop_id)
   {
-    case PROP_WIDGET:
-      priv->widget = GTK_WIDGET (g_value_get_object (value));
+    case PROP_STATE:
+      priv->state = g_value_get_enum (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
 }
+#endif
 
 static void
 cheese_widget_get_property (GObject *object, guint prop_id,
@@ -266,8 +271,8 @@ cheese_widget_get_property (GObject *object, guint prop_id,
 
   switch (prop_id)
   {
-    case PROP_WIDGET:
-      g_value_set_object (value, priv->widget);
+    case PROP_STATE:
+      g_value_set_enum (value, priv->state);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -275,7 +280,6 @@ cheese_widget_get_property (GObject *object, guint prop_id,
   }
 }
 
-#endif
 #if 0
 static void
 cheese_widget_changed (CheeseWidget *self)
@@ -284,19 +288,6 @@ cheese_widget_changed (CheeseWidget *self)
 
 #endif
 
-#if 0
-static gboolean
-cheese_widget_emit_error_idle (CheeseWidget *widget)
-{
-  CheeseWidgetPrivate *priv          = CHEESE_WIDGET_GET_PRIVATE (widget);
-  g_signal_emit (widget, widget_signals[ERROR_SIGNAL], 0,
-                 (priv->error && priv->error->message) ?
-                 priv->error->message :
-                 _("Camera setup failed"));
-  return FALSE;
-}
-#endif
-
 void
 setup_camera (CheeseWidget *widget)
 {
@@ -308,7 +299,6 @@ setup_camera (CheeseWidget *widget)
   gdouble              contrast;
   gdouble              saturation;
   gdouble              hue;
-  GError              *error = NULL;
 
   g_object_get (priv->gconf,
                 "gconf_prop_x_resolution", &x_resolution,
@@ -328,20 +318,22 @@ setup_camera (CheeseWidget *widget)
 
   g_free (webcam_device);
 
-  cheese_camera_setup (priv->webcam, NULL, &error);
+  cheese_camera_setup (priv->webcam, NULL, &priv->error);
 
   gdk_threads_enter ();
 
   gtk_spinner_stop (GTK_SPINNER (priv->spinner));
 
-  if (error != NULL)
+  if (priv->error != NULL)
   {
-    cheese_widget_set_problem_page (CHEESE_WIDGET (widget), "no-webcam");
-    g_signal_emit (widget, widget_signals[ERROR_SIGNAL], 0, error->message);
+    priv->state = CHEESE_WIDGET_STATE_ERROR;
+    g_object_notify (G_OBJECT (widget), "state");
+    cheese_widget_set_problem_page (CHEESE_WIDGET (widget), "error");
   }
   else
   {
-    g_signal_emit (widget, widget_signals[READY_SIGNAL], 0, TRUE);
+    priv->state = CHEESE_WIDGET_STATE_READY;
+    g_object_notify (G_OBJECT (widget), "state");
     cheese_camera_play (priv->webcam);
     gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), WEBCAM_PAGE);
   }
@@ -354,7 +346,6 @@ cheese_widget_realize (GtkWidget *widget)
 {
   GdkWindow           *window;
   CheeseWidgetPrivate *priv  = CHEESE_WIDGET_GET_PRIVATE (widget);
-  GError              *error = NULL;
 
   GTK_WIDGET_CLASS (cheese_widget_parent_class)->realize (widget);
 
@@ -373,9 +364,9 @@ cheese_widget_realize (GtkWidget *widget)
   gtk_widget_set_app_paintable (priv->screen, TRUE);
   gtk_widget_set_double_buffered (priv->screen, FALSE);
 
-  if (!g_thread_create ((GThreadFunc) setup_camera, widget, FALSE, &error))
+  if (!g_thread_create ((GThreadFunc) setup_camera, widget, FALSE, &priv->error))
   {
-    g_warning ("Failed to create setup thread: %s", error->message);
+    g_warning ("Failed to create setup thread: %s", priv->error->message);
     goto error;
   }
 
@@ -387,9 +378,9 @@ cheese_widget_realize (GtkWidget *widget)
 
 error:
   gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+  priv->state = CHEESE_WIDGET_STATE_ERROR;
+  g_object_notify (G_OBJECT (widget), "state");
   cheese_widget_set_problem_page (CHEESE_WIDGET (widget), "error");
-  g_signal_emit (widget, widget_signals[ERROR_SIGNAL], 0,
-                 error->message);
 }
 
 static void
@@ -401,39 +392,26 @@ cheese_widget_class_init (CheeseWidgetClass *klass)
   object_class->finalize = cheese_widget_finalize;
 #if 0
   object_class->set_property = cheese_widget_set_property;
-  object_class->get_property = cheese_widget_get_property;
 #endif
+  object_class->get_property = cheese_widget_get_property;
   widget_class->realize = cheese_widget_realize;
 
   /**
-   * CheeseWidget::ready:
-   *
-   * @is_ready: Whether the camera is ready for use by the widget.
-   *
-   * The ::ready signal is emitted when the camera is ready to be
-   * used by the widget and other applications.
-   */
-  widget_signals[READY_SIGNAL] = g_signal_new ("ready", G_OBJECT_CLASS_TYPE (klass),
-                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-                                               G_STRUCT_OFFSET (CheeseWidgetClass, ready),
-                                               NULL, NULL,
-                                               g_cclosure_marshal_VOID__BOOLEAN,
-                                               G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-  /**
-   * CheeseWidget::error:
+   * CheeseWidget:state:
    *
-   * @error: The error message.
+   * Current state of the widget.
    *
-   * The ::error signal is emitted when the widget cannot access
-   * the camera.
+   * Connect to notify::state signal to get notified about state
+   * changes. Useful to update other widgets sensitiveness when the
+   * camera is ready or to handle errors if camera setup fails.
    */
-  widget_signals[ERROR_SIGNAL] = g_signal_new ("error", G_OBJECT_CLASS_TYPE (klass),
-                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-                                               G_STRUCT_OFFSET (CheeseWidgetClass, error),
-                                               NULL, NULL,
-                                               g_cclosure_marshal_VOID__STRING,
-                                               G_TYPE_NONE, 1, G_TYPE_STRING);
+  g_object_class_install_property (object_class, PROP_STATE,
+                                   g_param_spec_enum ("state",
+                                                      NULL,
+                                                      NULL,
+                                                      CHEESE_TYPE_WIDGET_STATE,
+                                                      CHEESE_WIDGET_STATE_NONE,
+                                                      G_PARAM_READABLE));
 
   g_type_class_add_private (klass, sizeof (CheeseWidgetPrivate));
 }
@@ -487,6 +465,30 @@ cheese_widget_get_video_area (CheeseWidget *widget)
   return priv->screen;
 }
 
+/**
+ * cheese_widget_get_error:
+ * @widget: a #CheeseWidget
+ * @error: return location for the error
+ *
+ * Listen for notify::state signals and call this when the current state is %CHEESE_WIDGET_STATE_ERROR.
+ *
+ * The returned #GError will contain more details on what went wrong.
+ **/
+
+void
+cheese_widget_get_error (CheeseWidget *widget, GError **error)
+{
+  CheeseWidgetPrivate *priv;
+
+  g_return_if_fail (CHEESE_WIDGET (widget));
+
+  priv = CHEESE_WIDGET_GET_PRIVATE (widget);
+
+  g_propagate_error (error, priv->error);
+
+  priv->error = NULL;
+}
+
 /*
  * vim: sw=2 ts=8 cindent noai bs=2
  */
diff --git a/libcheese/cheese-widget.h b/libcheese/cheese-widget.h
index b8872ea..62c2092 100644
--- a/libcheese/cheese-widget.h
+++ b/libcheese/cheese-widget.h
@@ -41,9 +41,6 @@ typedef struct _CheeseWidget CheeseWidget;
 struct _CheeseWidgetClass
 {
   GtkNotebookClass parent_class;
-
-  void (*ready)(CheeseWidget *widget, gboolean is_ready);
-  void (*error)(CheeseWidget *widget, const char *error);
 };
 
 struct _CheeseWidget
@@ -54,6 +51,24 @@ struct _CheeseWidget
 GType cheese_widget_get_type (void) G_GNUC_CONST;
 
 GtkWidget *cheese_widget_new (void);
+void       cheese_widget_get_error (CheeseWidget *widget, GError **error);
+
+
+/**
+ * CheeseWidgetState:
+ * @CHEESE_WIDGET_STATE_NONE: Default state, camera uninitialized
+ * @CHEESE_WIDGET_STATE_READY: The camera should be ready and the widget should be displaying the preview
+ * @CHEESE_WIDGET_STATE_ERROR: An error occurred while setting up the camera, check what went wrong with cheese_widget_get_error()
+ *
+ * Current #CheeseWidget state.
+ *
+ */
+typedef enum
+{
+  CHEESE_WIDGET_STATE_NONE,
+  CHEESE_WIDGET_STATE_READY,
+  CHEESE_WIDGET_STATE_ERROR
+} CheeseWidgetState;
 
 G_END_DECLS
 



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