summaryrefslogtreecommitdiffstats
path: root/panels/user-accounts/cc-avatar-chooser.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/user-accounts/cc-avatar-chooser.c')
-rw-r--r--panels/user-accounts/cc-avatar-chooser.c663
1 files changed, 663 insertions, 0 deletions
diff --git a/panels/user-accounts/cc-avatar-chooser.c b/panels/user-accounts/cc-avatar-chooser.c
new file mode 100644
index 0000000..d0d4e1b
--- /dev/null
+++ b/panels/user-accounts/cc-avatar-chooser.c
@@ -0,0 +1,663 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010 Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gio/gunixoutputstream.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include <act/act.h>
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-desktop-thumbnail.h>
+
+#ifdef HAVE_CHEESE
+#include <cheese-avatar-chooser.h>
+#include <cheese-camera-device.h>
+#include <cheese-camera-device-monitor.h>
+#endif /* HAVE_CHEESE */
+
+#include "cc-avatar-chooser.h"
+#include "cc-crop-area.h"
+#include "user-utils.h"
+
+#define ROW_SPAN 5
+#define AVATAR_CHOOSER_PIXEL_SIZE 80
+#define PIXEL_SIZE 512
+
+struct _CcAvatarChooser {
+ GtkPopover parent;
+
+ GtkWidget *popup_button;
+ GtkWidget *crop_area;
+ GtkWidget *user_flowbox;
+ GtkWidget *flowbox;
+ GtkWidget *take_picture_button;
+
+#ifdef HAVE_CHEESE
+ CheeseCameraDeviceMonitor *monitor;
+ GCancellable *cancellable;
+ guint num_cameras;
+#endif /* HAVE_CHEESE */
+
+ GnomeDesktopThumbnailFactory *thumb_factory;
+ GListStore *faces;
+
+ ActUser *user;
+};
+
+G_DEFINE_TYPE (CcAvatarChooser, cc_avatar_chooser, GTK_TYPE_POPOVER)
+
+static void
+crop_dialog_response (CcAvatarChooser *self,
+ gint response_id,
+ GtkWidget *dialog)
+{
+ GdkPixbuf *pb, *pb2;
+
+ if (response_id != GTK_RESPONSE_ACCEPT) {
+ self->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ pb = cc_crop_area_get_picture (CC_CROP_AREA (self->crop_area));
+ pb2 = gdk_pixbuf_scale_simple (pb, PIXEL_SIZE, PIXEL_SIZE, GDK_INTERP_BILINEAR);
+
+ set_user_icon_data (self->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+
+ self->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+
+ gtk_popover_popdown (GTK_POPOVER (self));
+}
+
+static void
+cc_avatar_chooser_crop (CcAvatarChooser *self,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_dialog_new_with_buttons ("",
+ GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button)),
+ GTK_DIALOG_USE_HEADER_BAR,
+ _("_Cancel"),
+ GTK_RESPONSE_CANCEL,
+ _("Select"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
+
+ g_signal_connect_object (G_OBJECT (dialog), "response",
+ G_CALLBACK (crop_dialog_response), self, G_CONNECT_SWAPPED);
+
+ /* Content */
+ self->crop_area = cc_crop_area_new ();
+ gtk_widget_show (self->crop_area);
+ cc_crop_area_set_min_size (CC_CROP_AREA (self->crop_area), 48, 48);
+ cc_crop_area_set_constrain_aspect (CC_CROP_AREA (self->crop_area), TRUE);
+ cc_crop_area_set_picture (CC_CROP_AREA (self->crop_area), pixbuf);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ self->crop_area,
+ TRUE, TRUE, 8);
+
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+file_chooser_response (CcAvatarChooser *self,
+ gint response,
+ GtkDialog *chooser)
+{
+ gchar *filename;
+ GError *error;
+ GdkPixbuf *pixbuf, *pixbuf2;
+
+ if (response != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+ return;
+ }
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+
+ error = NULL;
+ pixbuf = gdk_pixbuf_new_from_file (filename, &error);
+ if (pixbuf == NULL) {
+ g_warning ("Failed to load %s: %s", filename, error->message);
+ g_error_free (error);
+ }
+ g_free (filename);
+
+ pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+
+ cc_avatar_chooser_crop (self, pixbuf2);
+ g_object_unref (pixbuf2);
+}
+
+static void
+update_preview (GtkFileChooser *chooser,
+ GnomeDesktopThumbnailFactory *thumb_factory)
+{
+ gchar *uri;
+
+ uri = gtk_file_chooser_get_uri (chooser);
+
+ if (uri) {
+ GdkPixbuf *pixbuf = NULL;
+ char *mime_type = NULL;
+ GFile *file;
+ GFileInfo *file_info;
+ GtkWidget *preview;
+
+ preview = gtk_file_chooser_get_preview_widget (chooser);
+
+ file = g_file_new_for_uri (uri);
+ file_info = g_file_query_info (file,
+ "standard::*",
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ g_object_unref (file);
+
+ if (file_info != NULL &&
+ g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) {
+ mime_type = g_strdup (g_file_info_get_content_type (file_info));
+ g_object_unref (file_info);
+ }
+
+ if (mime_type) {
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumb_factory,
+ uri,
+ mime_type);
+ g_free (mime_type);
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser),
+ GTK_RESPONSE_ACCEPT,
+ (pixbuf != NULL));
+
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else {
+ gtk_image_set_from_icon_name (GTK_IMAGE (preview),
+ "dialog-question",
+ GTK_ICON_SIZE_DIALOG);
+ }
+
+ g_free (uri);
+ }
+
+ gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+}
+
+static void
+cc_avatar_chooser_select_file (CcAvatarChooser *self)
+{
+ GtkWidget *chooser;
+ const gchar *folder;
+ GtkWidget *preview;
+ GtkFileFilter *filter;
+
+ chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"),
+ GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button)),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_modal (GTK_WINDOW (chooser), TRUE);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, 128, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (chooser), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (chooser), FALSE);
+ gtk_widget_show (preview);
+
+ /* Preview has to be generated after default handler of "selection-changed"
+ * signal, otherwise dialog response sensitivity is rewritten (Bug 547988).
+ * Preview also has to be generated on "selection-changed" signal to reflect
+ * all changes (Bug 660877). */
+ g_signal_connect_after (chooser, "selection-changed",
+ G_CALLBACK (update_preview), self->thumb_factory);
+
+ folder = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ if (folder)
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
+ folder);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (filter);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+ g_signal_connect_object (chooser, "response",
+ G_CALLBACK (file_chooser_response), self, G_CONNECT_SWAPPED);
+
+ gtk_window_present (GTK_WINDOW (chooser));
+}
+
+#ifdef HAVE_CHEESE
+static gboolean
+destroy_chooser (GtkWidget *chooser)
+{
+ gtk_widget_destroy (chooser);
+ return FALSE;
+}
+
+static void
+webcam_response_cb (CcAvatarChooser *self,
+ int response,
+ GtkDialog *dialog)
+{
+ if (response == GTK_RESPONSE_ACCEPT) {
+ GdkPixbuf *pb, *pb2;
+
+ g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL);
+ pb2 = gdk_pixbuf_scale_simple (pb, PIXEL_SIZE, PIXEL_SIZE, GDK_INTERP_BILINEAR);
+
+ set_user_icon_data (self->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+ }
+ if (response != GTK_RESPONSE_DELETE_EVENT &&
+ response != GTK_RESPONSE_NONE)
+ g_idle_add ((GSourceFunc) destroy_chooser, dialog);
+
+ gtk_popover_popdown (GTK_POPOVER (self));
+}
+
+static void
+webcam_icon_selected (CcAvatarChooser *self)
+{
+ GtkWidget *window;
+
+ window = cheese_avatar_chooser_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (window),
+ GTK_WINDOW (gtk_widget_get_toplevel (self->popup_button)));
+ gtk_window_set_modal (GTK_WINDOW (window), TRUE);
+ g_signal_connect_object (G_OBJECT (window), "response",
+ G_CALLBACK (webcam_response_cb), self, G_CONNECT_SWAPPED);
+ gtk_widget_show (window);
+}
+
+static void
+update_photo_menu_status (CcAvatarChooser *self)
+{
+ if (self->num_cameras == 0)
+ gtk_widget_set_visible (self->take_picture_button, FALSE);
+ else
+ gtk_widget_set_sensitive (self->take_picture_button, TRUE);
+}
+
+static void
+device_added (CcAvatarChooser *self)
+{
+ self->num_cameras++;
+ update_photo_menu_status (self);
+}
+
+static void
+device_removed (CcAvatarChooser *self)
+{
+ self->num_cameras--;
+ update_photo_menu_status (self);
+}
+
+#endif /* HAVE_CHEESE */
+
+static void
+face_widget_activated (CcAvatarChooser *self,
+ GtkFlowBoxChild *child)
+{
+ const gchar *filename;
+ GtkWidget *image;
+
+ image = gtk_bin_get_child (GTK_BIN (child));
+ filename = g_object_get_data (G_OBJECT (image), "filename");
+
+ act_user_set_icon_file (self->user, filename);
+
+ gtk_popover_popdown (GTK_POPOVER (self));
+}
+
+static GtkWidget *
+create_face_widget (gpointer item,
+ gpointer user_data)
+{
+ g_autofree gchar *image_path = NULL;
+ g_autoptr(GdkPixbuf) source_pixbuf = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkWidget *image;
+
+ image_path = g_file_get_path (G_FILE (item));
+
+ source_pixbuf = gdk_pixbuf_new_from_file_at_size (image_path,
+ AVATAR_CHOOSER_PIXEL_SIZE,
+ AVATAR_CHOOSER_PIXEL_SIZE,
+ NULL);
+ if (source_pixbuf == NULL)
+ return NULL;
+
+ pixbuf = round_image (source_pixbuf);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE);
+ gtk_widget_show (image);
+
+ g_object_set_data_full (G_OBJECT (image),
+ "filename", g_steal_pointer (&image_path), g_free);
+
+ return image;
+}
+
+#ifdef HAVE_CHEESE
+static void
+setup_cheese_camera_device_monitor (CcAvatarChooser *self)
+{
+ g_signal_connect_object (G_OBJECT (self->monitor), "added", G_CALLBACK (device_added), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (G_OBJECT (self->monitor), "removed", G_CALLBACK (device_removed), self, G_CONNECT_SWAPPED);
+ cheese_camera_device_monitor_coldplug (self->monitor);
+ update_photo_menu_status (self);
+}
+
+static void
+cheese_camera_device_monitor_new_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CcAvatarChooser *self = user_data;
+ GObject *ret;
+
+ ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, NULL);
+ if (ret == NULL)
+ return;
+
+ self->monitor = CHEESE_CAMERA_DEVICE_MONITOR (ret);
+ setup_cheese_camera_device_monitor (self);
+}
+#endif /* HAVE_CHEESE */
+
+static GStrv
+get_settings_facesdirs (void)
+{
+ g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
+ g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
+ GPtrArray *facesdirs = g_ptr_array_new ();
+
+ if (settings_dirs != NULL) {
+ int i;
+ for (i = 0; settings_dirs[i] != NULL; i++) {
+ char *path = settings_dirs[i];
+ if (g_strcmp0 (path, "") != 0)
+ g_ptr_array_add (facesdirs, g_strdup (path));
+ }
+ }
+ g_ptr_array_add (facesdirs, NULL);
+
+ return (GStrv) g_ptr_array_steal (facesdirs, NULL);
+}
+
+static GStrv
+get_system_facesdirs (void)
+{
+ const char * const * data_dirs;
+ GPtrArray *facesdirs;
+ int i;
+
+ facesdirs = g_ptr_array_new ();
+
+ data_dirs = g_get_system_data_dirs ();
+ for (i = 0; data_dirs[i] != NULL; i++) {
+ char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
+ g_ptr_array_add (facesdirs, path);
+ }
+ g_ptr_array_add (facesdirs, NULL);
+ return (GStrv) g_ptr_array_steal (facesdirs, NULL);
+}
+
+static gboolean
+add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
+{
+ GFile *file, *dir;
+ GFileInfo *info;
+ GFileEnumerator *enumerator;
+ GFileType type;
+ const gchar *target;
+ guint i;
+ gboolean added_faces = FALSE;
+
+ for (i = 0; facesdirs[i] != NULL; i++) {
+ dir = g_file_new_for_path (facesdirs[i]);
+
+ enumerator = g_file_enumerate_children (dir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+ G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ if (enumerator == NULL) {
+ g_object_unref (dir);
+ continue;
+ }
+
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+ type = g_file_info_get_file_type (info);
+ if (type != G_FILE_TYPE_REGULAR &&
+ type != G_FILE_TYPE_SYMBOLIC_LINK) {
+ g_object_unref (info);
+ continue;
+ }
+
+ target = g_file_info_get_symlink_target (info);
+ if (target != NULL && g_str_has_prefix (target , "legacy/")) {
+ g_object_unref (info);
+ continue;
+ }
+
+ file = g_file_get_child (dir, g_file_info_get_name (info));
+ g_list_store_append (faces, file);
+
+ g_object_unref (info);
+ added_faces = TRUE;
+ }
+
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ g_object_unref (enumerator);
+ g_object_unref (dir);
+
+ if (added_faces && !add_all)
+ break;
+ }
+ return added_faces;
+}
+
+
+static void
+setup_photo_popup (CcAvatarChooser *self)
+{
+ g_auto(GStrv) settings_facesdirs = NULL;
+
+ self->faces = g_list_store_new (G_TYPE_FILE);
+ gtk_flow_box_bind_model (GTK_FLOW_BOX (self->flowbox),
+ G_LIST_MODEL (self->faces),
+ create_face_widget,
+ self,
+ NULL);
+
+ g_signal_connect_object (self->flowbox, "child-activated",
+ G_CALLBACK (face_widget_activated), self, G_CONNECT_SWAPPED);
+
+ settings_facesdirs = get_settings_facesdirs ();
+
+ if (!add_faces_from_dirs (self->faces, settings_facesdirs, TRUE)) {
+ g_auto(GStrv) system_facesdirs = get_system_facesdirs ();
+ add_faces_from_dirs (self->faces, system_facesdirs, FALSE);
+ }
+
+#ifdef HAVE_CHEESE
+ gtk_widget_set_visible (self->take_picture_button, TRUE);
+
+ self->cancellable = g_cancellable_new ();
+ g_async_initable_new_async (CHEESE_TYPE_CAMERA_DEVICE_MONITOR,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ cheese_camera_device_monitor_new_cb,
+ self,
+ NULL);
+#endif /* HAVE_CHEESE */
+}
+
+static void
+popup_icon_menu (CcAvatarChooser *self)
+{
+ gtk_popover_popup (GTK_POPOVER (self));
+}
+
+static gboolean
+on_popup_button_button_pressed (CcAvatarChooser *self,
+ GdkEventButton *event)
+{
+ if (event->button == 1) {
+ if (!gtk_widget_get_visible (GTK_WIDGET (self))) {
+ popup_icon_menu (self);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->popup_button), TRUE);
+ } else {
+ gtk_popover_popdown (GTK_POPOVER (self));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->popup_button), FALSE);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+CcAvatarChooser *
+cc_avatar_chooser_new (GtkWidget *button)
+{
+ CcAvatarChooser *self;
+
+ self = g_object_new (CC_TYPE_AVATAR_CHOOSER,
+ "relative-to", button,
+ NULL);
+
+ self->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+
+ /* Set up the popup */
+ self->popup_button = button;
+ setup_photo_popup (self);
+ g_signal_connect_object (button, "toggled",
+ G_CALLBACK (popup_icon_menu), self, G_CONNECT_SWAPPED);
+ g_signal_connect_object (button, "button-press-event",
+ G_CALLBACK (on_popup_button_button_pressed), self, G_CONNECT_SWAPPED);
+
+ return self;
+}
+
+static void
+cc_avatar_chooser_dispose (GObject *object)
+{
+ CcAvatarChooser *self = CC_AVATAR_CHOOSER (object);
+
+ g_clear_object (&self->thumb_factory);
+#ifdef HAVE_CHEESE
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->monitor);
+#endif
+ g_clear_object (&self->user);
+
+ G_OBJECT_CLASS (cc_avatar_chooser_parent_class)->dispose (object);
+}
+
+static void
+cc_avatar_chooser_init (CcAvatarChooser *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+cc_avatar_chooser_class_init (CcAvatarChooserClass *klass)
+{
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/control-center/user-accounts/cc-avatar-chooser.ui");
+
+ gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, user_flowbox);
+ gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, flowbox);
+ gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, take_picture_button);
+
+ gtk_widget_class_bind_template_callback (wclass, cc_avatar_chooser_select_file);
+#ifdef HAVE_CHEESE
+ gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected);
+#endif
+
+ oclass->dispose = cc_avatar_chooser_dispose;
+}
+
+static void
+user_flowbox_activated (CcAvatarChooser *self)
+{
+ set_default_avatar (self->user);
+
+ gtk_popover_popdown (GTK_POPOVER (self));
+}
+
+void
+cc_avatar_chooser_set_user (CcAvatarChooser *self,
+ ActUser *user)
+{
+ g_autoptr(GdkPixbuf) source_pixbuf = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ GtkWidget *image;
+
+ g_return_if_fail (self != NULL);
+
+ if (self->user) {
+ gtk_container_foreach (GTK_CONTAINER (self->user_flowbox), (GtkCallback) gtk_widget_destroy, NULL);
+ g_object_unref (self->user);
+ self->user = NULL;
+ }
+ self->user = g_object_ref (user);
+
+ source_pixbuf = generate_default_avatar (user, AVATAR_CHOOSER_PIXEL_SIZE);
+ pixbuf = round_image (source_pixbuf);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE);
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (self->user_flowbox), image);
+ g_signal_connect_object (self->user_flowbox, "child-activated", G_CALLBACK (user_flowbox_activated), self, G_CONNECT_SWAPPED);
+}
+