GCC Code Coverage Report


Directory: ./
File: panels/system/users/cc-avatar-chooser.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 174 0.0%
Functions: 0 18 0.0%
Branches: 0 92 0.0%

Line Branch Exec Source
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 modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (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, see <http://www.gnu.org/licenses/>.
17 *
18 * Written by: Matthias Clasen <mclasen@redhat.com>
19 */
20
21 #include "config.h"
22
23 #include <stdlib.h>
24
25 #include <adwaita.h>
26 #include <gio/gunixoutputstream.h>
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <glib/gstdio.h>
30 #include <gtk/gtk.h>
31 #include <act/act.h>
32 #define GNOME_DESKTOP_USE_UNSTABLE_API
33 #include <libgnome-desktop/gnome-desktop-thumbnail.h>
34
35 #include "cc-avatar-chooser.h"
36 #include "cc-crop-area.h"
37 #include "user-utils.h"
38
39 #define ROW_SPAN 5
40 #define AVATAR_CHOOSER_PIXEL_SIZE 80
41
42 struct _CcAvatarChooser {
43 GtkPopover parent;
44
45 GtkWidget *crop_area;
46 GtkWidget *flowbox;
47
48 GnomeDesktopThumbnailFactory *thumb_factory;
49 GListStore *faces;
50
51 ActUser *user;
52 };
53
54 G_DEFINE_TYPE (CcAvatarChooser, cc_avatar_chooser, GTK_TYPE_POPOVER)
55
56 static void
57 crop_dialog_response (CcAvatarChooser *self,
58 gint response_id,
59 GtkWidget *dialog)
60 {
61 g_autoptr(GdkPixbuf) pb = NULL;
62 g_autoptr(GdkPixbuf) pb2 = NULL;
63 g_autoptr(GdkTexture) texture = NULL;
64
65 if (response_id != GTK_RESPONSE_ACCEPT) {
66 self->crop_area = NULL;
67 gtk_window_destroy (GTK_WINDOW (dialog));
68 return;
69 }
70
71 pb = cc_crop_area_create_pixbuf (CC_CROP_AREA (self->crop_area));
72 pb2 = gdk_pixbuf_scale_simple (pb, AVATAR_PIXEL_SIZE, AVATAR_PIXEL_SIZE, GDK_INTERP_BILINEAR);
73 texture = gdk_texture_new_for_pixbuf (pb2);
74
75 set_user_icon_data (self->user, texture, IMAGE_SOURCE_VALUE_CUSTOM);
76
77 self->crop_area = NULL;
78 gtk_window_destroy (GTK_WINDOW (dialog));
79
80 gtk_popover_popdown (GTK_POPOVER (self));
81 }
82
83 static void
84 cc_avatar_chooser_crop (CcAvatarChooser *self,
85 GdkPixbuf *pixbuf)
86 {
87 GtkWidget *dialog;
88
89 dialog = gtk_dialog_new_with_buttons ("",
90 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
91 GTK_DIALOG_USE_HEADER_BAR,
92 _("_Cancel"),
93 GTK_RESPONSE_CANCEL,
94 _("Select"),
95 GTK_RESPONSE_ACCEPT,
96 NULL);
97 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
98
99 gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
100
101 g_signal_connect_object (G_OBJECT (dialog), "response",
102 G_CALLBACK (crop_dialog_response), self, G_CONNECT_SWAPPED);
103
104 /* Content */
105 self->crop_area = cc_crop_area_new ();
106 cc_crop_area_set_min_size (CC_CROP_AREA (self->crop_area), 48, 48);
107 cc_crop_area_set_paintable (CC_CROP_AREA (self->crop_area),
108 GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf)));
109 gtk_box_prepend (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
110 self->crop_area);
111 gtk_widget_set_hexpand (self->crop_area, TRUE);
112 gtk_widget_set_vexpand (self->crop_area, TRUE);
113
114 gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
115
116 gtk_window_present (GTK_WINDOW (dialog));
117 }
118
119 static void
120 file_dialog_open_cb (GObject *source_object,
121 GAsyncResult *res,
122 gpointer user_data)
123 {
124 CcAvatarChooser *self = CC_AVATAR_CHOOSER (user_data);
125 GtkFileDialog *file_dialog = GTK_FILE_DIALOG (source_object);
126 g_autoptr(GError) error = NULL;
127 g_autoptr(GdkPixbuf) pixbuf = NULL;
128 g_autoptr(GdkPixbuf) pixbuf2 = NULL;
129 g_autoptr(GFile) file = NULL;
130 g_autoptr(GFileInputStream) stream = NULL;
131
132 file = gtk_file_dialog_open_finish (file_dialog, res, &error);
133
134 if (error != NULL) {
135 g_warning ("Failed to pick avatar image: %s", error->message);
136 return;
137 }
138
139 stream = g_file_read (file, NULL, &error);
140 pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (stream),
141 NULL, &error);
142 if (pixbuf == NULL) {
143 g_warning ("Failed to load %s: %s", g_file_get_uri (file), error->message);
144 }
145
146 pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf);
147
148 cc_avatar_chooser_crop (self, pixbuf2);
149 }
150
151 static void
152 cc_avatar_chooser_select_file (CcAvatarChooser *self)
153 {
154 g_autoptr(GFile) pictures_folder = NULL;
155 GtkFileDialog *file_dialog;
156 GtkFileFilter *filter;
157 GListStore *filters;
158
159 g_return_if_fail (CC_IS_AVATAR_CHOOSER (self));
160
161 file_dialog = gtk_file_dialog_new ();
162 gtk_file_dialog_set_title (file_dialog, _("Browse for more pictures"));
163 gtk_file_dialog_set_modal (file_dialog, TRUE);
164
165 filter = gtk_file_filter_new ();
166 gtk_file_filter_add_pixbuf_formats (filter);
167
168 filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
169 g_list_store_append (filters, filter);
170 gtk_file_dialog_set_filters (file_dialog, G_LIST_MODEL (filters));
171
172 pictures_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
173 gtk_file_dialog_set_initial_folder (file_dialog, pictures_folder);
174
175 gtk_popover_popdown (GTK_POPOVER (self));
176 gtk_file_dialog_open (file_dialog,
177 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
178 NULL,
179 file_dialog_open_cb,
180 self);
181 }
182
183 static void
184 face_widget_activated (CcAvatarChooser *self,
185 GtkFlowBoxChild *child)
186 {
187 const gchar *filename;
188 GtkWidget *image;
189 g_autoptr(GdkTexture) texture = NULL;
190 g_autoptr(GError) error = NULL;
191
192 image = gtk_flow_box_child_get_child (child);
193 filename = g_object_get_data (G_OBJECT (image), "filename");
194
195 if (filename != NULL) {
196 texture = gdk_texture_new_from_filename (filename, &error);
197 }
198
199 if (error != NULL) {
200 g_warning ("Failed to load selected avatar image: %s", error->message);
201 } else {
202 set_user_icon_data (self->user, texture, IMAGE_SOURCE_VALUE_FACE);
203 }
204
205 gtk_popover_popdown (GTK_POPOVER (self));
206 }
207
208 static GtkWidget *
209 create_face_widget (gpointer item,
210 gpointer user_data)
211 {
212 g_autofree gchar *image_path = NULL;
213 g_autoptr(GtkWidget) avatar = NULL;
214 g_autoptr(GdkTexture) source_image = NULL;
215
216 image_path = g_file_get_path (G_FILE (item));
217
218 avatar = adw_avatar_new (AVATAR_CHOOSER_PIXEL_SIZE, NULL, false);
219
220 if (image_path) {
221 source_image = gdk_texture_new_from_filename (image_path, NULL);
222 g_object_set_data_full (G_OBJECT (avatar),
223 "filename", g_steal_pointer (&image_path), g_free);
224 }
225
226 if (source_image == NULL) {
227 adw_avatar_set_icon_name (ADW_AVATAR (avatar), "image-missing");
228 } else {
229 adw_avatar_set_custom_image (ADW_AVATAR (avatar), GDK_PAINTABLE (source_image));
230 }
231
232 return g_steal_pointer (&avatar);
233 }
234
235 static GStrv
236 get_settings_facesdirs (void)
237 {
238 g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
239 g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
240 g_autoptr(GPtrArray) facesdirs = g_ptr_array_new ();
241
242 if (settings_dirs != NULL) {
243 int i;
244 for (i = 0; settings_dirs[i] != NULL; i++) {
245 char *path = settings_dirs[i];
246 if (g_strcmp0 (path, "") != 0)
247 g_ptr_array_add (facesdirs, g_strdup (path));
248 }
249 }
250 g_ptr_array_add (facesdirs, NULL);
251
252 return (GStrv) g_ptr_array_steal (facesdirs, NULL);
253 }
254
255 static GStrv
256 get_system_facesdirs (void)
257 {
258 const char * const * data_dirs;
259 g_autoptr(GPtrArray) facesdirs = NULL;
260 int i;
261
262 facesdirs = g_ptr_array_new ();
263
264 data_dirs = g_get_system_data_dirs ();
265 for (i = 0; data_dirs[i] != NULL; i++) {
266 char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
267 g_ptr_array_add (facesdirs, path);
268 }
269 g_ptr_array_add (facesdirs, NULL);
270 return (GStrv) g_ptr_array_steal (facesdirs, NULL);
271 }
272
273 static gboolean
274 add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
275 {
276 gboolean added_faces = FALSE;
277
278 for (guint i = 0; facesdirs[i] != NULL; i++) {
279 g_autoptr(GFile) dir = NULL;
280 g_autoptr(GFileEnumerator) enumerator = NULL;
281
282 dir = g_file_new_for_path (facesdirs[i]);
283
284 enumerator = g_file_enumerate_children (dir,
285 G_FILE_ATTRIBUTE_STANDARD_NAME ","
286 G_FILE_ATTRIBUTE_STANDARD_TYPE ","
287 G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
288 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
289 G_FILE_QUERY_INFO_NONE,
290 NULL, NULL);
291 if (enumerator == NULL) {
292 continue;
293 }
294
295 while (TRUE) {
296 GFile *file;
297 GFileType type;
298 const gchar *target;
299 g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, NULL, NULL);
300 if (info == NULL) {
301 break;
302 }
303
304 type = g_file_info_get_file_type (info);
305 if (type != G_FILE_TYPE_REGULAR &&
306 type != G_FILE_TYPE_SYMBOLIC_LINK) {
307 continue;
308 }
309
310 target = g_file_info_get_attribute_byte_string (info,
311 G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
312 if (target != NULL && g_str_has_prefix (target , "legacy/")) {
313 continue;
314 }
315
316 file = g_file_get_child (dir, g_file_info_get_name (info));
317 g_list_store_append (faces, file);
318
319 added_faces = TRUE;
320 }
321
322 g_file_enumerator_close (enumerator, NULL, NULL);
323
324 if (added_faces && !add_all)
325 break;
326 }
327 return added_faces;
328 }
329
330
331 static void
332 setup_photo_popup (CcAvatarChooser *self)
333 {
334 g_auto(GStrv) settings_facesdirs = NULL;
335
336 self->faces = g_list_store_new (G_TYPE_FILE);
337 gtk_flow_box_bind_model (GTK_FLOW_BOX (self->flowbox),
338 G_LIST_MODEL (self->faces),
339 create_face_widget,
340 self,
341 NULL);
342
343 g_signal_connect_object (self->flowbox, "child-activated",
344 G_CALLBACK (face_widget_activated), self, G_CONNECT_SWAPPED);
345
346 settings_facesdirs = get_settings_facesdirs ();
347
348 if (!add_faces_from_dirs (self->faces, settings_facesdirs, TRUE)) {
349 g_auto(GStrv) system_facesdirs = get_system_facesdirs ();
350 add_faces_from_dirs (self->faces, system_facesdirs, FALSE);
351 }
352 }
353
354 CcAvatarChooser *
355 cc_avatar_chooser_new (void)
356 {
357 return g_object_new (CC_TYPE_AVATAR_CHOOSER,
358 NULL);
359 }
360
361 static void
362 cc_avatar_chooser_dispose (GObject *object)
363 {
364 CcAvatarChooser *self = CC_AVATAR_CHOOSER (object);
365
366 g_clear_object (&self->thumb_factory);
367 g_clear_object (&self->user);
368
369 G_OBJECT_CLASS (cc_avatar_chooser_parent_class)->dispose (object);
370 }
371
372 static void
373 cc_avatar_chooser_init (CcAvatarChooser *self)
374 {
375 gtk_widget_init_template (GTK_WIDGET (self));
376
377 self->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
378
379 setup_photo_popup (self);
380 }
381
382 static void
383 cc_avatar_chooser_class_init (CcAvatarChooserClass *klass)
384 {
385 GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
386 GObjectClass *oclass = G_OBJECT_CLASS (klass);
387
388 gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/control-center/system/users/cc-avatar-chooser.ui");
389
390 gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, flowbox);
391
392 gtk_widget_class_bind_template_callback (wclass, cc_avatar_chooser_select_file);
393
394 oclass->dispose = cc_avatar_chooser_dispose;
395 }
396
397 void
398 cc_avatar_chooser_set_user (CcAvatarChooser *self,
399 ActUser *user)
400 {
401 g_return_if_fail (self != NULL);
402
403 g_clear_object (&self->user);
404 self->user = g_object_ref (user);
405 }
406