1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright 2009-2010 Red Hat, Inc,
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA
19 *
20 * Written by: Matthias Clasen <mclasen@redhat.com>
21 */
22
23 #include "config.h"
24
25 #include <stdlib.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30
31 #ifdef HAVE_CHEESE
32 #include <cheese-avatar-chooser.h>
33 #include <cheese-camera-device.h>
34 #include <cheese-camera-device-monitor.h>
35 #endif /* HAVE_CHEESE */
36
37 #include "um-photo-dialog.h"
38 #include "um-utils.h"
39
40 #define ROW_SPAN 5
41 #define AVATAR_PIXEL_SIZE 72
42
43 struct _UmPhotoDialog {
44 GtkPopover parent;
45
46 GtkWidget *popup_button;
47 GtkWidget *take_picture_button;
48 GtkWidget *flowbox;
49 GtkWidget *recent_pictures;
50
51 #ifdef HAVE_CHEESE
52 CheeseCameraDeviceMonitor *monitor;
53 GCancellable *cancellable;
54 guint num_cameras;
55 #endif /* HAVE_CHEESE */
56
57 GListStore *recent_faces;
58 GListStore *faces;
59 GFile *generated_avatar;
60 gboolean custom_avatar_was_chosen;
61
62 SelectAvatarCallback *callback;
63 gpointer data;
64 };
65
G_DEFINE_TYPE(UmPhotoDialog,um_photo_dialog,GTK_TYPE_POPOVER)66 G_DEFINE_TYPE (UmPhotoDialog, um_photo_dialog, GTK_TYPE_POPOVER)
67
68 #ifdef HAVE_CHEESE
69 static gboolean
70 destroy_chooser (GtkWidget *chooser)
71 {
72 gtk_widget_destroy (chooser);
73 return FALSE;
74 }
75
76 static void
webcam_response_cb(GtkDialog * dialog,int response,UmPhotoDialog * um)77 webcam_response_cb (GtkDialog *dialog,
78 int response,
79 UmPhotoDialog *um)
80 {
81 if (response == GTK_RESPONSE_ACCEPT) {
82 GdkPixbuf *pb, *pb2;
83
84 g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL);
85 pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
86
87 um->callback (pb2, NULL, um->data);
88 um->custom_avatar_was_chosen = TRUE;
89
90 g_object_unref (pb2);
91 g_object_unref (pb);
92 }
93 if (response != GTK_RESPONSE_DELETE_EVENT &&
94 response != GTK_RESPONSE_NONE)
95 g_idle_add ((GSourceFunc) destroy_chooser, dialog);
96 }
97
98 static void
webcam_icon_selected(UmPhotoDialog * um)99 webcam_icon_selected (UmPhotoDialog *um)
100 {
101 GtkWidget *window;
102
103 window = cheese_avatar_chooser_new ();
104 gtk_window_set_transient_for (GTK_WINDOW (window),
105 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (um))));
106 gtk_window_set_modal (GTK_WINDOW (window), TRUE);
107 g_signal_connect (G_OBJECT (window), "response",
108 G_CALLBACK (webcam_response_cb), um);
109 gtk_widget_show (window);
110
111 gtk_popover_popdown (GTK_POPOVER (um));
112 }
113
114 static void
update_photo_menu_status(UmPhotoDialog * um)115 update_photo_menu_status (UmPhotoDialog *um)
116 {
117 gtk_widget_set_visible (um->take_picture_button, um->num_cameras != 0);
118 }
119
120 static void
device_added(CheeseCameraDeviceMonitor * monitor,CheeseCameraDevice * device,UmPhotoDialog * um)121 device_added (CheeseCameraDeviceMonitor *monitor,
122 CheeseCameraDevice *device,
123 UmPhotoDialog *um)
124 {
125 um->num_cameras++;
126 update_photo_menu_status (um);
127 }
128
129 static void
device_removed(CheeseCameraDeviceMonitor * monitor,const char * id,UmPhotoDialog * um)130 device_removed (CheeseCameraDeviceMonitor *monitor,
131 const char *id,
132 UmPhotoDialog *um)
133 {
134 um->num_cameras--;
135 update_photo_menu_status (um);
136 }
137
138 static void
setup_cheese_camera_device_monitor(UmPhotoDialog * um)139 setup_cheese_camera_device_monitor (UmPhotoDialog *um)
140 {
141 g_signal_connect (G_OBJECT (um->monitor), "added", G_CALLBACK (device_added), um);
142 g_signal_connect (G_OBJECT (um->monitor), "removed", G_CALLBACK (device_removed), um);
143 cheese_camera_device_monitor_coldplug (um->monitor);
144 }
145
146 static void
cheese_camera_device_monitor_new_cb(GObject * source,GAsyncResult * result,gpointer user_data)147 cheese_camera_device_monitor_new_cb (GObject *source,
148 GAsyncResult *result,
149 gpointer user_data)
150 {
151 UmPhotoDialog *um = user_data;
152 GObject *ret;
153
154 ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, NULL);
155 if (ret == NULL)
156 return;
157
158 um->monitor = CHEESE_CAMERA_DEVICE_MONITOR (ret);
159 setup_cheese_camera_device_monitor (um);
160 }
161 #else /* ! HAVE_CHEESE */
162 static void
163 webcam_icon_selected (UmPhotoDialog *um)
164 {
165 g_warning ("Webcam icon selected, but compiled without Cheese support");
166 }
167 #endif /* HAVE_CHEESE */
168
169 static void
face_widget_activated(GtkFlowBox * flowbox,GtkFlowBoxChild * child,UmPhotoDialog * um)170 face_widget_activated (GtkFlowBox *flowbox,
171 GtkFlowBoxChild *child,
172 UmPhotoDialog *um)
173 {
174 const char *filename;
175 GtkWidget *image;
176
177 image = gtk_bin_get_child (GTK_BIN (child));
178 filename = g_object_get_data (G_OBJECT (image), "filename");
179
180 um->callback (NULL, filename, um->data);
181 um->custom_avatar_was_chosen = TRUE;
182
183 gtk_popover_popdown (GTK_POPOVER (um));
184 }
185
186 static void
generated_avatar_activated(GtkFlowBox * flowbox,GtkFlowBoxChild * child,UmPhotoDialog * um)187 generated_avatar_activated (GtkFlowBox *flowbox,
188 GtkFlowBoxChild *child,
189 UmPhotoDialog *um)
190 {
191 face_widget_activated (flowbox, child, um);
192 um->custom_avatar_was_chosen = FALSE;
193 }
194
195 static GtkWidget *
create_face_widget(gpointer item,gpointer user_data)196 create_face_widget (gpointer item,
197 gpointer user_data)
198 {
199 g_autoptr(GdkPixbuf) pixbuf = NULL;
200 GtkWidget *image;
201 g_autofree gchar *path = g_file_get_path (G_FILE (item));
202
203 pixbuf = gdk_pixbuf_new_from_file_at_size (path,
204 AVATAR_PIXEL_SIZE,
205 AVATAR_PIXEL_SIZE,
206 NULL);
207
208 if (pixbuf != NULL)
209 image = gtk_image_new_from_pixbuf (round_image (pixbuf));
210 else
211 image = gtk_image_new ();
212
213 gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_PIXEL_SIZE);
214
215 gtk_widget_show (image);
216
217 g_object_set_data_full (G_OBJECT (image),
218 "filename", g_steal_pointer (&path),
219 (GDestroyNotify) g_free);
220
221 return image;
222 }
223
224 static GStrv
get_settings_facesdirs(void)225 get_settings_facesdirs (void)
226 {
227 g_autoptr(GSettingsSchema) schema = NULL;
228 g_autoptr(GPtrArray) facesdirs = g_ptr_array_new ();
229 g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
230 g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
231
232 if (settings_dirs != NULL) {
233 int i;
234 for (i = 0; settings_dirs[i] != NULL; i++) {
235 char *path = settings_dirs[i];
236 if (path != NULL && g_strcmp0 (path, "") != 0)
237 g_ptr_array_add (facesdirs, g_strdup (path));
238 }
239 }
240
241 // NULL terminated array
242 g_ptr_array_add (facesdirs, NULL);
243 return (GStrv) g_steal_pointer (&facesdirs->pdata);
244 }
245
246 static GStrv
get_system_facesdirs(void)247 get_system_facesdirs (void)
248 {
249 g_autoptr(GPtrArray) facesdirs = NULL;
250 const char * const * data_dirs;
251 int i;
252
253 facesdirs = g_ptr_array_new ();
254
255 data_dirs = g_get_system_data_dirs ();
256 for (i = 0; data_dirs[i] != NULL; i++) {
257 char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
258 g_ptr_array_add (facesdirs, path);
259 }
260
261 // NULL terminated array
262 g_ptr_array_add (facesdirs, NULL);
263 return (GStrv) g_steal_pointer (&facesdirs->pdata);
264 }
265
266 static gboolean
add_faces_from_dirs(GListStore * faces,GStrv facesdirs,gboolean add_all)267 add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
268 {
269 gboolean added_faces = FALSE;
270 const gchar *target;
271 int i;
272 GFileType type;
273
274 for (i = 0; facesdirs[i] != NULL; i++) {
275 g_autoptr(GFileEnumerator) enumerator = NULL;
276 g_autoptr(GFile) dir = NULL;
277 const char *path = facesdirs[i];
278 gpointer infoptr;
279
280 dir = g_file_new_for_path (path);
281 enumerator = g_file_enumerate_children (dir,
282 G_FILE_ATTRIBUTE_STANDARD_NAME ","
283 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
284 G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
285 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
286 G_FILE_QUERY_INFO_NONE,
287 NULL, NULL);
288
289 if (enumerator == NULL)
290 continue;
291
292 while ((infoptr = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
293 g_autoptr (GFileInfo) info = infoptr;
294 g_autoptr (GFile) face_file = NULL;
295
296 type = g_file_info_get_file_type (info);
297 if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_SYMBOLIC_LINK)
298 continue;
299
300 target = g_file_info_get_symlink_target (info);
301 if (target != NULL && g_str_has_prefix (target , "legacy/"))
302 continue;
303
304 face_file = g_file_get_child (dir, g_file_info_get_name (info));
305 g_list_store_append (faces, face_file);
306 added_faces = TRUE;
307 }
308
309 g_file_enumerator_close (enumerator, NULL, NULL);
310
311 if (added_faces && !add_all)
312 break;
313 }
314 return added_faces;
315 }
316
317 static void
setup_photo_popup(UmPhotoDialog * um)318 setup_photo_popup (UmPhotoDialog *um)
319 {
320 g_auto(GStrv) facesdirs;
321 gboolean added_faces = FALSE;
322
323 um->faces = g_list_store_new (G_TYPE_FILE);
324 gtk_flow_box_bind_model (GTK_FLOW_BOX (um->flowbox),
325 G_LIST_MODEL (um->faces),
326 create_face_widget,
327 um,
328 NULL);
329
330 g_signal_connect (um->flowbox, "child-activated",
331 G_CALLBACK (face_widget_activated), um);
332
333 um->recent_faces = g_list_store_new (G_TYPE_FILE);
334 gtk_flow_box_bind_model (GTK_FLOW_BOX (um->recent_pictures),
335 G_LIST_MODEL (um->recent_faces),
336 create_face_widget,
337 um,
338 NULL);
339 g_signal_connect (um->recent_pictures, "child-activated",
340 G_CALLBACK (generated_avatar_activated), um);
341 um->custom_avatar_was_chosen = FALSE;
342
343 facesdirs = get_settings_facesdirs ();
344 added_faces = add_faces_from_dirs (um->faces, facesdirs, TRUE);
345
346 if (!added_faces) {
347 facesdirs = get_system_facesdirs ();
348 add_faces_from_dirs (um->faces, facesdirs, FALSE);
349 }
350
351 #ifdef HAVE_CHEESE
352 um->cancellable = g_cancellable_new ();
353 g_async_initable_new_async (CHEESE_TYPE_CAMERA_DEVICE_MONITOR,
354 G_PRIORITY_DEFAULT,
355 um->cancellable,
356 cheese_camera_device_monitor_new_cb,
357 um,
358 NULL);
359 #endif /* HAVE_CHEESE */
360 }
361
362 static void
popup_icon_menu(GtkToggleButton * button,UmPhotoDialog * um)363 popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um)
364 {
365 gtk_popover_popup (GTK_POPOVER (um));
366 }
367
368 static gboolean
on_popup_button_button_pressed(GtkToggleButton * button,GdkEventButton * event,UmPhotoDialog * um)369 on_popup_button_button_pressed (GtkToggleButton *button,
370 GdkEventButton *event,
371 UmPhotoDialog *um)
372 {
373 if (event->button == 1) {
374 if (!gtk_widget_get_visible (GTK_WIDGET (um))) {
375 popup_icon_menu (button, um);
376 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
377 } else {
378 gtk_popover_popdown (GTK_POPOVER (um));
379 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
380 }
381
382 return TRUE;
383 }
384
385 return FALSE;
386 }
387
388 void
um_photo_dialog_generate_avatar(UmPhotoDialog * um,const gchar * name)389 um_photo_dialog_generate_avatar (UmPhotoDialog *um,
390 const gchar *name)
391 {
392 cairo_surface_t *surface;
393 gchar *filename;
394
395 surface = generate_user_picture (name);
396
397 /* Save into a tmp file that later gets copied by AccountsService */
398 filename = g_build_filename (g_get_user_runtime_dir (), "avatar.png", NULL);
399 um->generated_avatar = g_file_new_for_path (filename);
400 cairo_surface_write_to_png (surface, g_file_get_path (um->generated_avatar));
401 g_free (filename);
402
403 /* Overwrite the first item */
404 if (g_list_model_get_item (G_LIST_MODEL (um->recent_faces), 0) != NULL)
405 g_list_store_remove (um->recent_faces, 0);
406
407 g_list_store_insert (um->recent_faces, 0,
408 um->generated_avatar);
409 gtk_widget_show_all (um->recent_pictures);
410
411 if (!um->custom_avatar_was_chosen) {
412 um->callback (NULL, g_file_get_path (um->generated_avatar), um->data);
413 }
414 }
415
416 UmPhotoDialog *
um_photo_dialog_new(GtkWidget * button,SelectAvatarCallback callback,gpointer data)417 um_photo_dialog_new (GtkWidget *button,
418 SelectAvatarCallback callback,
419 gpointer data)
420 {
421 UmPhotoDialog *um;
422
423 um = g_object_new (UM_TYPE_PHOTO_DIALOG,
424 "relative-to", button,
425 NULL);
426
427 /* Set up the popup */
428 um->popup_button = button;
429 setup_photo_popup (um);
430 g_signal_connect (button, "toggled",
431 G_CALLBACK (popup_icon_menu), um);
432 g_signal_connect (button, "button-press-event",
433 G_CALLBACK (on_popup_button_button_pressed), um);
434
435 um->callback = callback;
436 um->data = data;
437
438 return um;
439 }
440
441 void
um_photo_dialog_dispose(GObject * object)442 um_photo_dialog_dispose (GObject *object)
443 {
444 #ifdef HAVE_CHEESE
445 UmPhotoDialog *um = UM_PHOTO_DIALOG (object);
446
447 g_cancellable_cancel (um->cancellable);
448 g_clear_object (&um->cancellable);
449 g_clear_object (&um->monitor);
450 #endif
451
452 G_OBJECT_CLASS (um_photo_dialog_parent_class)->dispose (object);
453 }
454
455 static void
um_photo_dialog_init(UmPhotoDialog * um)456 um_photo_dialog_init (UmPhotoDialog *um)
457 {
458 gtk_widget_init_template (GTK_WIDGET (um));
459 }
460
461 static void
um_photo_dialog_class_init(UmPhotoDialogClass * klass)462 um_photo_dialog_class_init (UmPhotoDialogClass *klass)
463 {
464 GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
465 GObjectClass *oclass = G_OBJECT_CLASS (klass);
466
467 gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/initial-setup/gis-account-avatar-chooser.ui");
468
469 gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, flowbox);
470 gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, recent_pictures);
471 gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, take_picture_button);
472 gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected);
473
474 oclass->dispose = um_photo_dialog_dispose;
475 }
476