/* * Copyright © 2009 Bastien Nocera * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "cheese-widget.h" #include "cheese-widget-private.h" #include "cheese-camera.h" #include "cheese-enums.h" #include "totem-aspect-frame.h" /** * SECTION:cheese-widget * @short_description: A photo/video capture widget * @stability: Unstable * @include: cheese/cheese-widget.h * * #CheeseWidget provides a basic photo and video capture widget, for embedding * in an application. */ enum { READY_SIGNAL, ERROR_SIGNAL, LAST_SIGNAL }; enum { PROP_0, PROP_STATE, PROP_LAST }; static GParamSpec *properties[PROP_LAST]; /** * CheeseWidget: * * Use the accessor functions below. */ struct _CheeseWidget { /*< private >*/ GtkNotebook parent_instance; void *unused; }; typedef struct { GtkWidget *spinner; GtkWidget *screen; ClutterActor *texture; GtkWidget *problem; GSettings *settings; CheeseCamera *webcam; CheeseWidgetState state; GError *error; } CheeseWidgetPrivate; G_DEFINE_TYPE_WITH_PRIVATE (CheeseWidget, cheese_widget, GTK_TYPE_NOTEBOOK) static GdkPixbuf * cheese_widget_load_pixbuf (GtkWidget *widget, const char *icon_name, guint size, GError **error) { GtkIconTheme *theme; GdkPixbuf *pixbuf; theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); /* FIXME special case "no-webcam" and actually use the icon_name */ pixbuf = gtk_icon_theme_load_icon (theme, "dialog-error", size, GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE, error); return pixbuf; } static gboolean cheese_widget_logo_draw (GtkWidget *w, cairo_t *cr, gpointer user_data) { const char *icon_name; GdkPixbuf *pixbuf, *logo; GError *error = NULL; GtkAllocation allocation; guint s_width, s_height, d_width, d_height; float ratio; gtk_widget_get_allocation (w, &allocation); cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); icon_name = g_object_get_data (G_OBJECT (w), "icon-name"); if (icon_name == NULL) { cairo_paint (cr); return FALSE; } cairo_rectangle (cr, 0, 0, allocation.width, allocation.height); d_width = allocation.width; d_height = allocation.height - (allocation.height / 3); pixbuf = cheese_widget_load_pixbuf (w, icon_name, d_height, &error); if (pixbuf == NULL) { g_warning ("Could not load icon '%s': %s", icon_name, error->message); g_error_free (error); return FALSE; } s_width = gdk_pixbuf_get_width (pixbuf); s_height = gdk_pixbuf_get_height (pixbuf); if ((gfloat) d_width / s_width > (gfloat) d_height / s_height) { ratio = (gfloat) d_height / s_height; } else { ratio = (gfloat) d_width / s_width; } s_width *= ratio; s_height *= ratio; logo = gdk_pixbuf_scale_simple (pixbuf, s_width, s_height, GDK_INTERP_BILINEAR); gdk_cairo_set_source_pixbuf (cr, logo, (allocation.width - s_width) / 2, (allocation.height - s_height) / 2); cairo_paint (cr); g_object_unref (logo); g_object_unref (pixbuf); return FALSE; } static void cheese_widget_spinner_invert (GtkWidget *spinner, GtkWidget *parent) { GtkStyleContext *context; guint i; for (i = GTK_STATE_NORMAL; i <= GTK_STATE_INSENSITIVE; i++) { GdkRGBA fg, bg; context = gtk_widget_get_style_context (spinner); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg); gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg); gtk_widget_override_color (spinner, i, &bg); gtk_widget_override_background_color (spinner, i, &fg); gtk_widget_override_color (parent, i, &bg); gtk_widget_override_background_color (parent, i, &fg); } } static void cheese_widget_set_problem_page (CheeseWidget *widget, const char *icon_name) { CheeseWidgetPrivate *priv = cheese_widget_get_instance_private (widget); priv->state = CHEESE_WIDGET_STATE_ERROR; g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_STATE]); gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), PROBLEM_PAGE); g_object_set_data_full (G_OBJECT (priv->problem), "icon-name", g_strdup (icon_name), g_free); g_signal_connect (priv->problem, "draw", G_CALLBACK (cheese_widget_logo_draw), widget); } static void cheese_widget_init (CheeseWidget *widget) { CheeseWidgetPrivate *priv = cheese_widget_get_instance_private (widget); GtkWidget *box; ClutterActor *stage, *frame; ClutterColor black = { 0x00, 0x00, 0x00, 0xff }; 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); gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE); /* Spinner page */ priv->spinner = gtk_spinner_new (); box = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (box), priv->spinner); cheese_widget_spinner_invert (priv->spinner, box); gtk_widget_show_all (box); gtk_notebook_append_page (GTK_NOTEBOOK (widget), box, gtk_label_new ("spinner")); /* Webcam page */ priv->screen = gtk_clutter_embed_new (); gtk_widget_set_size_request (priv->screen, 460, 345); stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->screen)); clutter_actor_set_background_color (stage, &black); frame = totem_aspect_frame_new (); priv->texture = clutter_actor_new (); totem_aspect_frame_set_child (TOTEM_ASPECT_FRAME (frame), priv->texture); clutter_actor_set_layout_manager (stage, clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL)); clutter_actor_add_child (stage, frame); gtk_widget_show (priv->screen); clutter_actor_show (priv->texture); gtk_notebook_append_page (GTK_NOTEBOOK (widget), priv->screen, gtk_label_new ("webcam")); /* Problem page */ priv->problem = gtk_drawing_area_new (); gtk_widget_show (priv->problem); gtk_notebook_append_page (GTK_NOTEBOOK (widget), priv->problem, gtk_label_new ("got problems")); priv->settings = g_settings_new ("org.gnome.Cheese"); } static void cheese_widget_finalize (GObject *object) { CheeseWidgetPrivate *priv = cheese_widget_get_instance_private (CHEESE_WIDGET (object)); g_clear_object (&priv->settings); g_clear_object (&priv->webcam); G_OBJECT_CLASS (cheese_widget_parent_class)->finalize (object); } static void cheese_widget_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CheeseWidgetPrivate *priv; g_return_if_fail (CHEESE_IS_WIDGET (object)); priv = cheese_widget_get_instance_private (CHEESE_WIDGET (object)); switch (prop_id) { case PROP_STATE: g_value_set_enum (value, priv->state); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /* * webcam_state_changed: * @camera: the camera on which there was a state change * @state: the new state * @widget: the widget which should be updated * * Handle the state of the @camera changing, and update @widget accordingly, * such as by displaying an error. */ static void webcam_state_changed (CheeseCamera *camera, GstState state, CheeseWidget *widget) { if (state == GST_STATE_NULL) { cheese_widget_set_problem_page (widget, "error"); /* TODO: Handle other cases. */ } } static void setup_camera (CheeseWidget *widget) { CheeseWidgetPrivate *priv = cheese_widget_get_instance_private (widget); gchar *webcam_device; gint x_resolution; gint y_resolution; x_resolution = g_settings_get_int (priv->settings, "photo-x-resolution"); y_resolution = g_settings_get_int (priv->settings, "photo-y-resolution"); webcam_device = g_settings_get_string (priv->settings, "camera"); priv->webcam = cheese_camera_new (priv->texture, webcam_device, x_resolution, y_resolution); g_free (webcam_device); cheese_camera_setup (priv->webcam, NULL, &priv->error); gtk_spinner_stop (GTK_SPINNER (priv->spinner)); if (priv->error != NULL) { cheese_widget_set_problem_page (CHEESE_WIDGET (widget), "error"); } else { gdouble brightness; gdouble contrast; gdouble saturation; gdouble hue; brightness = g_settings_get_double (priv->settings, "brightness"); contrast = g_settings_get_double (priv->settings, "contrast"); saturation = g_settings_get_double (priv->settings, "saturation"); hue = g_settings_get_double (priv->settings, "hue"); cheese_camera_set_balance_property (priv->webcam, "brightness", brightness); cheese_camera_set_balance_property (priv->webcam, "contrast", contrast); cheese_camera_set_balance_property (priv->webcam, "saturation", saturation); cheese_camera_set_balance_property (priv->webcam, "hue", hue); priv->state = CHEESE_WIDGET_STATE_READY; g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_STATE]); g_signal_connect (priv->webcam, "state-flags-changed", G_CALLBACK (webcam_state_changed), widget); cheese_camera_play (priv->webcam); gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), WEBCAM_PAGE); } } static void cheese_widget_realize (GtkWidget *widget) { CheeseWidgetPrivate *priv; priv = cheese_widget_get_instance_private (CHEESE_WIDGET (widget)); GTK_WIDGET_CLASS (cheese_widget_parent_class)->realize (widget); gtk_spinner_start (GTK_SPINNER (priv->spinner)); gtk_widget_realize (priv->screen); setup_camera (CHEESE_WIDGET (widget)); gtk_widget_set_app_paintable (priv->problem, TRUE); gtk_widget_realize (priv->problem); return; } static void cheese_widget_class_init (CheeseWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cheese_widget_finalize; object_class->get_property = cheese_widget_get_property; widget_class->realize = cheese_widget_realize; /** * CheeseWidget:state: * * Current state of the widget. * * Connect to notify::state signal to get notified about state changes. * Useful to update other widgets sensitivities when the camera is ready or * to handle errors if camera setup fails. */ properties[PROP_STATE] = g_param_spec_enum ("state", "State", "The current state of the widget", CHEESE_TYPE_WIDGET_STATE, CHEESE_WIDGET_STATE_NONE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST, properties); } /** * cheese_widget_new: * * Creates a new #CheeseWidget. Make sure that you call cheese_gtk_init(), and * check for errors during initialization, before calling this function. * * Returns: a new #CheeseWidget */ GtkWidget * cheese_widget_new (void) { return g_object_new (CHEESE_TYPE_WIDGET, NULL); } GSettings * cheese_widget_get_settings (CheeseWidget *widget) { CheeseWidgetPrivate *priv; g_return_val_if_fail (CHEESE_WIDGET (widget), NULL); priv = cheese_widget_get_instance_private (widget); return priv->settings; } /* * cheese_widget_get_camera: * @widget: a #CheeseWidget * * Returns: a #CheeseCamera for this #CheeseWidget */ GObject * cheese_widget_get_camera (CheeseWidget *widget) { CheeseWidgetPrivate *priv; g_return_val_if_fail (CHEESE_WIDGET (widget), NULL); priv = cheese_widget_get_instance_private (widget); return G_OBJECT (priv->webcam); } /* * cheese_widget_get_video_area: * @widget: a #CheeseWidget * * Returns: a #GtkClutterEmbed of the stream from the video capture device */ GtkWidget * cheese_widget_get_video_area (CheeseWidget *widget) { CheeseWidgetPrivate *priv; g_return_val_if_fail (CHEESE_WIDGET (widget), NULL); priv = cheese_widget_get_instance_private (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_instance_private (widget); g_propagate_error (error, priv->error); priv->error = NULL; }