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 |
|
|
|