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"
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
55 static GParamSpec
*properties
[PROP_LAST
];
57 struct _CheeseWidgetPrivate
61 ClutterActor
*texture
;
65 CheeseWidgetState state
;
69 G_DEFINE_TYPE_WITH_PRIVATE (CheeseWidget
, cheese_widget
, GTK_TYPE_NOTEBOOK
);
71 void setup_camera (CheeseWidget
*widget
);
74 cheese_widget_load_pixbuf (GtkWidget
*widget
,
75 const char *icon_name
,
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
);
91 cheese_widget_logo_draw (GtkWidget
*w
,
95 const char *icon_name
;
96 GdkPixbuf
*pixbuf
, *logo
;
98 GtkAllocation allocation
;
99 guint s_width
, s_height
, d_width
, d_height
;
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
) {
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
);
120 g_warning ("Could not load icon '%s': %s", icon_name
, error
->message
);
121 g_error_free (error
);
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
;
134 ratio
= (gfloat
) d_width
/ s_width
;
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);
145 g_object_unref (logo
);
146 g_object_unref (pixbuf
);
152 cheese_widget_spinner_invert (GtkWidget
*spinner
, GtkWidget
*parent
)
154 GtkStyleContext
*context
;
157 for (i
= GTK_STATE_NORMAL
; i
<= GTK_STATE_INSENSITIVE
; i
++)
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
);
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
);
190 cheese_widget_init (CheeseWidget
*widget
)
192 CheeseWidgetPrivate
*priv
= widget
->priv
= cheese_widget_get_instance_private (widget
);
194 ClutterActor
*stage
, *frame
;
195 ClutterColor black
= { 0x00, 0x00, 0x00, 0xff };
197 priv
->state
= CHEESE_WIDGET_STATE_NONE
;
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
);
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"));
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"));
234 priv
->problem
= gtk_drawing_area_new ();
235 gtk_widget_show (priv
->problem
);
236 gtk_notebook_append_page (GTK_NOTEBOOK (widget
),
238 gtk_label_new ("got problems"));
240 priv
->settings
= g_settings_new ("org.gnome.Cheese");
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
);
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
));
265 g_value_set_enum (value
, priv
->state
);
268 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
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.
283 webcam_state_changed (CheeseCamera
*camera
, GstState state
,
284 CheeseWidget
*widget
)
289 cheese_widget_set_problem_page (widget
, "error");
292 /* TODO: Handle other cases. */
298 setup_camera (CheeseWidget
*widget
)
300 CheeseWidgetPrivate
*priv
= widget
->priv
;
301 gchar
*webcam_device
= NULL
;
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
),
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");
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
);
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
);
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
;
374 object_class
->set_property
= cheese_widget_set_property
;
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",
390 "The current state of the widget",
391 CHEESE_TYPE_WIDGET_STATE
,
392 CHEESE_WIDGET_STATE_NONE
,
394 G_PARAM_STATIC_STRINGS
);
396 g_object_class_install_properties (object_class
, PROP_LAST
, properties
);
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
408 cheese_widget_new (void)
410 return g_object_new (CHEESE_TYPE_WIDGET
, NULL
);
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
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
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.
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
);