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