Post-release version bump to 3.11.91
[cheese.git] / libcheese / cheese-widget.c
blob7ba1a820b032d0935a3e00c0ee4b115238fdd908
1 /*
2 * Copyright © 2009 Bastien Nocera <hadess@hadess.net>
4 * Licensed under the GNU General Public License Version 2
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "cheese-config.h"
22 #include <glib/gi18n.h>
23 #include <clutter-gst/clutter-gst.h>
25 #include "cheese-widget.h"
26 #include "cheese-widget-private.h"
27 #include "cheese-camera.h"
28 #include "cheese-enums.h"
29 #include "totem-aspect-frame.h"
31 /**
32 * SECTION:cheese-widget
33 * @short_description: A photo/video capture widget
34 * @stability: Unstable
35 * @include: cheese/cheese-widget.h
37 * #CheeseWidget provides a basic photo and video capture widget, for embedding
38 * in an application.
41 enum
43 READY_SIGNAL,
44 ERROR_SIGNAL,
45 LAST_SIGNAL
48 enum
50 PROP_0,
51 PROP_STATE,
52 PROP_LAST
55 static GParamSpec *properties[PROP_LAST];
57 struct _CheeseWidgetPrivate
59 GtkWidget *spinner;
60 GtkWidget *screen;
61 ClutterActor *texture;
62 GtkWidget *problem;
63 GSettings *settings;
64 CheeseCamera *webcam;
65 CheeseWidgetState state;
66 GError *error;
69 G_DEFINE_TYPE_WITH_PRIVATE (CheeseWidget, cheese_widget, GTK_TYPE_NOTEBOOK);
71 void setup_camera (CheeseWidget *widget);
73 static GdkPixbuf *
74 cheese_widget_load_pixbuf (GtkWidget *widget,
75 const char *icon_name,
76 guint size,
77 GError **error)
79 GtkIconTheme *theme;
80 GdkPixbuf *pixbuf;
82 theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
84 /* FIXME special case "no-webcam" and actually use the icon_name */
85 pixbuf = gtk_icon_theme_load_icon (theme, "dialog-error",
86 size, GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE, error);
87 return pixbuf;
90 static gboolean
91 cheese_widget_logo_draw (GtkWidget *w,
92 cairo_t *cr,
93 gpointer user_data)
95 const char *icon_name;
96 GdkPixbuf *pixbuf, *logo;
97 GError *error = NULL;
98 GtkAllocation allocation;
99 guint s_width, s_height, d_width, d_height;
100 float ratio;
102 gtk_widget_get_allocation (w, &allocation);
104 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
106 icon_name = g_object_get_data (G_OBJECT (w), "icon-name");
107 if (icon_name == NULL) {
108 cairo_paint (cr);
109 return FALSE;
112 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
114 d_width = allocation.width;
115 d_height = allocation.height - (allocation.height / 3);
117 pixbuf = cheese_widget_load_pixbuf (w, icon_name, d_height, &error);
118 if (pixbuf == NULL)
120 g_warning ("Could not load icon '%s': %s", icon_name, error->message);
121 g_error_free (error);
122 return FALSE;
125 s_width = gdk_pixbuf_get_width (pixbuf);
126 s_height = gdk_pixbuf_get_height (pixbuf);
128 if ((gfloat) d_width / s_width > (gfloat) d_height / s_height)
130 ratio = (gfloat) d_height / s_height;
132 else
134 ratio = (gfloat) d_width / s_width;
137 s_width *= ratio;
138 s_height *= ratio;
140 logo = gdk_pixbuf_scale_simple (pixbuf, s_width, s_height, GDK_INTERP_BILINEAR);
142 gdk_cairo_set_source_pixbuf (cr, logo, (allocation.width - s_width) / 2, (allocation.height - s_height) / 2);
143 cairo_paint (cr);
145 g_object_unref (logo);
146 g_object_unref (pixbuf);
148 return FALSE;
151 static void
152 cheese_widget_spinner_invert (GtkWidget *spinner, GtkWidget *parent)
154 GtkStyleContext *context;
155 guint i;
157 for (i = GTK_STATE_NORMAL; i <= GTK_STATE_INSENSITIVE; i++)
159 GdkRGBA fg, bg;
161 context = gtk_widget_get_style_context (spinner);
162 gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg);
163 gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg);
165 gtk_widget_override_color (spinner, i, &bg);
166 gtk_widget_override_background_color (spinner, i, &fg);
168 gtk_widget_override_color (parent, i, &bg);
169 gtk_widget_override_background_color (parent, i, &fg);
173 static void
174 cheese_widget_set_problem_page (CheeseWidget *widget,
175 const char *icon_name)
177 CheeseWidgetPrivate *priv = widget->priv;
179 priv->state = CHEESE_WIDGET_STATE_ERROR;
180 g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_STATE]);
182 gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), PROBLEM_PAGE);
183 g_object_set_data_full (G_OBJECT (priv->problem),
184 "icon-name", g_strdup (icon_name), g_free);
185 g_signal_connect (priv->problem, "draw",
186 G_CALLBACK (cheese_widget_logo_draw), widget);
189 static void
190 cheese_widget_init (CheeseWidget *widget)
192 CheeseWidgetPrivate *priv = widget->priv = cheese_widget_get_instance_private (widget);
193 GtkWidget *box;
194 ClutterActor *stage, *frame;
195 ClutterColor black = { 0x00, 0x00, 0x00, 0xff };
197 priv->state = CHEESE_WIDGET_STATE_NONE;
198 priv->error = NULL;
200 /* XXX
201 * remove this line if you want to debug */
202 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
203 gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
205 /* Spinner page */
206 priv->spinner = gtk_spinner_new ();
207 box = gtk_event_box_new ();
208 gtk_container_add (GTK_CONTAINER (box), priv->spinner);
209 cheese_widget_spinner_invert (priv->spinner, box);
210 gtk_widget_show_all (box);
212 gtk_notebook_append_page (GTK_NOTEBOOK (widget),
213 box, gtk_label_new ("spinner"));
215 /* Webcam page */
216 priv->screen = gtk_clutter_embed_new ();
217 gtk_widget_set_size_request (priv->screen, 460, 345);
218 stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->screen));
219 clutter_actor_set_background_color (stage, &black);
220 frame = totem_aspect_frame_new ();
222 priv->texture = clutter_texture_new ();
223 totem_aspect_frame_set_child (TOTEM_ASPECT_FRAME (frame), priv->texture);
225 clutter_actor_set_layout_manager (stage, clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
226 clutter_actor_add_child (stage, frame);
228 gtk_widget_show (priv->screen);
229 clutter_actor_show (priv->texture);
230 gtk_notebook_append_page (GTK_NOTEBOOK (widget),
231 priv->screen, gtk_label_new ("webcam"));
233 /* Problem page */
234 priv->problem = gtk_drawing_area_new ();
235 gtk_widget_show (priv->problem);
236 gtk_notebook_append_page (GTK_NOTEBOOK (widget),
237 priv->problem,
238 gtk_label_new ("got problems"));
240 priv->settings = g_settings_new ("org.gnome.Cheese");
243 static void
244 cheese_widget_finalize (GObject *object)
246 CheeseWidgetPrivate *priv = ((CheeseWidget *) object)->priv;
248 g_clear_object (&priv->settings);
249 g_clear_object (&priv->webcam);
251 G_OBJECT_CLASS (cheese_widget_parent_class)->finalize (object);
254 static void
255 cheese_widget_get_property (GObject *object, guint prop_id,
256 GValue *value, GParamSpec *pspec)
258 CheeseWidgetPrivate *priv = ((CheeseWidget *) object)->priv;
260 g_return_if_fail (CHEESE_IS_WIDGET (object));
262 switch (prop_id)
264 case PROP_STATE:
265 g_value_set_enum (value, priv->state);
266 break;
267 default:
268 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
269 break;
274 * webcam_state_changed:
275 * @camera: the camera on which there was a state change
276 * @state: the new state
277 * @widget: the widget which should be updated
279 * Handle the state of the @camera changing, and update @widget accordingly,
280 * such as by displaying an error.
282 static void
283 webcam_state_changed (CheeseCamera *camera, GstState state,
284 CheeseWidget *widget)
286 switch (state)
288 case GST_STATE_NULL:
289 cheese_widget_set_problem_page (widget, "error");
290 break;
291 default:
292 /* TODO: Handle other cases. */
293 break;
297 void
298 setup_camera (CheeseWidget *widget)
300 CheeseWidgetPrivate *priv = widget->priv;
301 gchar *webcam_device = NULL;
302 gint x_resolution;
303 gint y_resolution;
304 gdouble brightness;
305 gdouble contrast;
306 gdouble saturation;
307 gdouble hue;
309 g_settings_get (priv->settings, "photo-x-resolution", "i", &x_resolution);
310 g_settings_get (priv->settings, "photo-y-resolution", "i", &y_resolution);
311 g_settings_get (priv->settings, "camera", "s", &webcam_device);
312 g_settings_get (priv->settings, "brightness", "d", &brightness);
313 g_settings_get (priv->settings, "contrast", "d", &contrast);
314 g_settings_get (priv->settings, "saturation", "d", &saturation);
315 g_settings_get (priv->settings, "hue", "d", &hue);
317 priv->webcam = cheese_camera_new (CLUTTER_TEXTURE (priv->texture),
318 webcam_device,
319 x_resolution,
320 y_resolution);
322 g_free (webcam_device);
324 cheese_camera_setup (priv->webcam, NULL, &priv->error);
326 gtk_spinner_stop (GTK_SPINNER (priv->spinner));
328 if (priv->error != NULL)
330 cheese_widget_set_problem_page (CHEESE_WIDGET (widget), "error");
332 else
334 cheese_camera_set_balance_property (priv->webcam, "brightness", brightness);
335 cheese_camera_set_balance_property (priv->webcam, "contrast", contrast);
336 cheese_camera_set_balance_property (priv->webcam, "saturation", saturation);
337 cheese_camera_set_balance_property (priv->webcam, "hue", hue);
338 priv->state = CHEESE_WIDGET_STATE_READY;
339 g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_STATE]);
340 g_signal_connect (priv->webcam, "state-flags-changed",
341 G_CALLBACK (webcam_state_changed), widget);
342 cheese_camera_play (priv->webcam);
343 gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), WEBCAM_PAGE);
347 static void
348 cheese_widget_realize (GtkWidget *widget)
350 CheeseWidgetPrivate *priv = ((CheeseWidget *) widget)->priv;
352 GTK_WIDGET_CLASS (cheese_widget_parent_class)->realize (widget);
354 gtk_spinner_start (GTK_SPINNER (priv->spinner));
356 gtk_widget_realize (priv->screen);
358 setup_camera (CHEESE_WIDGET (widget));
360 gtk_widget_set_app_paintable (priv->problem, TRUE);
361 gtk_widget_realize (priv->problem);
363 return;
366 static void
367 cheese_widget_class_init (CheeseWidgetClass *klass)
369 GObjectClass *object_class = G_OBJECT_CLASS (klass);
370 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
372 object_class->finalize = cheese_widget_finalize;
373 #if 0
374 object_class->set_property = cheese_widget_set_property;
375 #endif
376 object_class->get_property = cheese_widget_get_property;
377 widget_class->realize = cheese_widget_realize;
380 * CheeseWidget:state:
382 * Current state of the widget.
384 * Connect to notify::state signal to get notified about state changes.
385 * Useful to update other widgets sensitivities when the camera is ready or
386 * to handle errors if camera setup fails.
388 properties[PROP_STATE] = g_param_spec_enum ("state",
389 "State",
390 "The current state of the widget",
391 CHEESE_TYPE_WIDGET_STATE,
392 CHEESE_WIDGET_STATE_NONE,
393 G_PARAM_READABLE |
394 G_PARAM_STATIC_STRINGS);
396 g_object_class_install_properties (object_class, PROP_LAST, properties);
400 * cheese_widget_new:
402 * Creates a new #CheeseWidget. Make sure that you call cheese_gtk_init(), and
403 * check for errors during initialization, before calling this function.
405 * Returns: a new #CheeseWidget
407 GtkWidget *
408 cheese_widget_new (void)
410 return g_object_new (CHEESE_TYPE_WIDGET, NULL);
413 GSettings *
414 cheese_widget_get_settings (CheeseWidget *widget)
416 g_return_val_if_fail (CHEESE_WIDGET (widget), NULL);
418 return widget->priv->settings;
422 * cheese_widget_get_camera:
423 * @widget: a #CheeseWidget
425 * Returns: a #CheeseCamera for this #CheeseWidget
427 GObject *
428 cheese_widget_get_camera (CheeseWidget *widget)
430 g_return_val_if_fail (CHEESE_WIDGET (widget), NULL);
432 return G_OBJECT (widget->priv->webcam);
436 * cheese_widget_get_video_area:
437 * @widget: a #CheeseWidget
439 * Returns: a #GtkClutterEmbed of the stream from the video capture device
441 GtkWidget *
442 cheese_widget_get_video_area (CheeseWidget *widget)
444 g_return_val_if_fail (CHEESE_WIDGET (widget), NULL);
446 return widget->priv->screen;
450 * cheese_widget_get_error:
451 * @widget: a #CheeseWidget
452 * @error: return location for the error
454 * Listen for notify::state signals and call this when the current state is
455 * %CHEESE_WIDGET_STATE_ERROR.
457 * The returned #GError will contain more details on what went wrong.
459 void
460 cheese_widget_get_error (CheeseWidget *widget, GError **error)
462 CheeseWidgetPrivate *priv;
464 g_return_if_fail (CHEESE_WIDGET (widget));
466 priv = (widget)->priv;
468 g_propagate_error (error, priv->error);
470 priv->error = NULL;