[gdm/wip/initial-setup2] initial-setup: add an avatar chooser



commit 827552bbd814a6755dd799b5c27df3f5bed5847b
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon May 7 20:54:36 2012 -0400

    initial-setup: add an avatar chooser
    
    This code is adapted from the user panel.

 configure.ac                          |    6 +
 gui/initial-setup/Makefile.am         |    3 +
 gui/initial-setup/gdm-initial-setup.c |   98 ++++
 gui/initial-setup/setup.ui            |   18 +
 gui/initial-setup/um-crop-area.c      |  818 +++++++++++++++++++++++++++++++++
 gui/initial-setup/um-crop-area.h      |   65 +++
 gui/initial-setup/um-photo-dialog.c   |  599 ++++++++++++++++++++++++
 gui/initial-setup/um-photo-dialog.h   |   41 ++
 8 files changed, 1648 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index fdc298e..39d76e4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -180,6 +180,10 @@ PKG_CHECK_MODULES(INITIAL_SETUP,
                   libnm-util >= $NETWORK_MANAGER_REQUIRED_VERSION
                   accountsservice
                   gnome-keyring-1
+                  gnome-desktop-3.0
+                  gstreamer-0.10
+                  cheese
+                  cheese-gtk >= 3.3.5
                   gweather-3.0
                   goa-1.0
                   goa-backend-1.0
@@ -188,6 +192,8 @@ PKG_CHECK_MODULES(INITIAL_SETUP,
                   gio-2.0 >= $GLIB_REQUIRED_VERSION
                   gio-unix-2.0 >= $GLIB_REQUIRED_VERSION)
 
+AC_DEFINE(HAVE_CHEESE, 1, [Define to 1 to enable cheese webcam support])
+
 # Unit testing framework
 PKG_CHECK_MODULES(CHECK,
                   [check >= 0.9.4],
diff --git a/gui/initial-setup/Makefile.am b/gui/initial-setup/Makefile.am
index d964057..e73c0b8 100644
--- a/gui/initial-setup/Makefile.am
+++ b/gui/initial-setup/Makefile.am
@@ -8,6 +8,7 @@ AM_CPPFLAGS = \
 	$(INITIAL_SETUP_CFLAGS) \
 	-DUIDIR="\"$(uidir)\"" \
 	-DGNOMECC_DATA_DIR="\"$(datadir)/gnome-control-center\"" \
+	-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
 	-DDATADIR="\"$(datadir)/gnome-control-center/ui/datetime\"" \
 	-I../libgdmgreeter
 
@@ -33,6 +34,8 @@ gdm_initial_setup_SOURCES =	\
 	panel-cell-renderer-security.c panel-cell-renderer-security.h \
 	cc-timezone-map.c cc-timezone-map.h \
 	um-utils.c um-utils.h \
+	um-crop-area.c um-crop-area.h \
+	um-photo-dialog.c um-photo-dialog.h \
 	tz.c tz.h \
 	../libgdmgreeter/gdm-greeter-client.c ../libgdmgreeter/gdm-greeter-client.h \
 	$(dbus_built_sources) \
diff --git a/gui/initial-setup/gdm-initial-setup.c b/gui/initial-setup/gdm-initial-setup.c
index 1f67b6c..b86ece7 100644
--- a/gui/initial-setup/gdm-initial-setup.c
+++ b/gui/initial-setup/gdm-initial-setup.c
@@ -1,5 +1,7 @@
 #include <config.h>
 #include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixoutputstream.h>
 
 #include <stdlib.h>
 #include <gtk/gtk.h>
@@ -19,6 +21,7 @@
 #include "cc-timezone-map.h"
 #include "timedated.h"
 #include "um-utils.h"
+#include "um-photo-dialog.h"
 #include "gdm-greeter-client.h"
 
 #define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
@@ -29,6 +32,10 @@
 #define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE
 #include <goabackend/goabackend.h>
 
+#ifdef HAVE_CHEESE
+#include <cheese-gtk.h>
+#endif
+
 #include <gnome-keyring.h>
 
 #define DEFAULT_TZ "Europe/London"
@@ -62,6 +69,10 @@ typedef struct {
 
         gboolean user_data_unsaved;
 
+        GtkWidget *photo_dialog;
+        GdkPixbuf *avatar_pixbuf;
+        gchar *avatar_filename;
+
         /* location data */
         CcTimezoneMap *map;
         TzLocation *current_location;
@@ -991,6 +1002,46 @@ confirm_entry_focus_out (GtkWidget     *entry,
 }
 
 static void
+set_user_avatar (SetupData *setup)
+{
+        gchar *path;
+        gint fd;
+        GOutputStream *stream;
+        GError *error;
+
+        if (setup->avatar_filename != NULL) {
+                act_user_set_icon_file (setup->act_user, setup->avatar_filename);
+                return;
+        }
+
+        if (setup->avatar_pixbuf == NULL) {
+                return;
+        }
+
+        path = g_build_filename (g_get_tmp_dir (), "usericonXXXXXX", NULL);
+        fd = g_mkstemp (path);
+        if (fd == -1) {
+                g_warning ("failed to create temporary file for image data");
+                g_free (path);
+                return;
+        }
+
+        stream = g_unix_output_stream_new (fd, TRUE);
+
+        error = NULL;
+        if (!gdk_pixbuf_save_to_stream (setup->avatar_pixbuf, stream, "png", NULL, &error, NULL)) {
+                g_warning ("failed to save image: %s", error->message);
+                g_error_free (error);
+                g_object_unref (stream);
+                return;
+        }
+
+        g_object_unref (stream);
+
+        act_user_set_icon_file (setup->act_user, path); 
+}
+
+static void
 create_user (SetupData *setup)
 {
         const gchar *username;
@@ -1006,6 +1057,8 @@ create_user (SetupData *setup)
                 g_warning ("Failed to create user: %s", error->message);
                 g_error_free (error);
         }
+
+        set_user_avatar (setup);
 }
 
 static void save_account_data (SetupData *setup);
@@ -1219,6 +1272,39 @@ account_row_activated (GtkTreeView       *tv,
 }
 
 static void
+avatar_callback (GdkPixbuf   *pixbuf,
+                 const gchar *filename,
+                 gpointer     data)
+{
+        SetupData *setup = data;
+        GtkWidget *image;
+        GdkPixbuf *tmp;
+
+        g_clear_object (&setup->avatar_pixbuf);
+        g_free (setup->avatar_filename);
+        setup->avatar_filename = NULL;
+
+        image = WID("local-account-avatar-image");
+
+        if (pixbuf) {
+                setup->avatar_pixbuf = g_object_ref (pixbuf);
+                tmp = gdk_pixbuf_scale_simple (pixbuf, 64, 64, GDK_INTERP_BILINEAR);
+                gtk_image_set_from_pixbuf (GTK_IMAGE (image), tmp);
+                g_object_unref (tmp);
+        }
+        else if (filename) {
+                setup->avatar_filename = g_strdup (filename);
+                tmp = gdk_pixbuf_new_from_file_at_size (filename, 64, 64, NULL);
+                gtk_image_set_from_pixbuf (GTK_IMAGE (image), tmp);
+                g_object_unref (tmp);
+        }
+        else {
+                gtk_image_set_from_icon_name (GTK_IMAGE (image), "avatar-default",
+                                                                GTK_ICON_SIZE_DIALOG);
+        }
+}
+
+static void
 prepare_account_page (SetupData *setup)
 {
         GtkWidget *fullname_entry;
@@ -1229,6 +1315,7 @@ prepare_account_page (SetupData *setup)
         GtkWidget *confirm_entry;
         GtkWidget *local_account_cancel_button;
         GtkWidget *local_account_done_button;
+        GtkWidget *local_account_avatar_button;
         GtkTreeViewColumn *col;
         GtkCellRenderer *cell;
 
@@ -1259,6 +1346,10 @@ prepare_account_page (SetupData *setup)
         confirm_entry = WID("account-confirm-entry");
         local_account_cancel_button = WID("local-account-cancel-button");
         local_account_done_button = WID("local-account-done-button");
+        local_account_avatar_button = WID("local-account-avatar-button");
+        setup->photo_dialog = (GtkWidget *)um_photo_dialog_new (local_account_avatar_button,
+                                                                avatar_callback,
+                                                                setup);
 
         g_signal_connect (fullname_entry, "notify::text",
                           G_CALLBACK (fullname_changed), setup);
@@ -2044,6 +2135,13 @@ main (int argc, char *argv[])
                 { NULL, 0 }
         };
 
+        bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+        bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+#ifdef HAVE_CHEESE
+        cheese_gtk_init (NULL, NULL);
+#endif
+
         setup = g_new0 (SetupData, 1);
 
         gtk_init_with_args (&argc, &argv, "", entries, GETTEXT_PACKAGE, NULL);
diff --git a/gui/initial-setup/setup.ui b/gui/initial-setup/setup.ui
index 6fb3d8f..2fdccae 100644
--- a/gui/initial-setup/setup.ui
+++ b/gui/initial-setup/setup.ui
@@ -81,6 +81,24 @@
             <property name="column-spacing">12</property>
             <property name="margin-bottom">18</property>
             <child>
+              <object class="GtkToggleButton" id="local-account-avatar-button">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkImage" id="local-account-avatar-image">
+                    <property name="visible">True</property>
+                    <property name="icon-name">avatar-default</property>
+                    <property name="pixel-size">64</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left-attach">2</property>
+                <property name="top-attach">1</property>
+                <property name="width">1</property>
+                <property name="height">2</property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkLabel" id="account-fullname-label">
                 <property name="visible">True</property>
                 <property name="label" translatable="yes">_Fullname</property>
diff --git a/gui/initial-setup/um-crop-area.c b/gui/initial-setup/um-crop-area.c
new file mode 100644
index 0000000..94ddd0d
--- /dev/null
+++ b/gui/initial-setup/um-crop-area.c
@@ -0,0 +1,818 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009  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 3 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "um-crop-area.h"
+
+struct _UmCropAreaPrivate {
+        GdkPixbuf *browse_pixbuf;
+        GdkPixbuf *pixbuf;
+        GdkPixbuf *color_shifted;
+        gdouble scale;
+        GdkRectangle image;
+        GdkCursorType current_cursor;
+        GdkRectangle crop;
+        gint active_region;
+        gint last_press_x;
+        gint last_press_y;
+        gint base_width;
+        gint base_height;
+        gdouble aspect;
+};
+
+G_DEFINE_TYPE (UmCropArea, um_crop_area, GTK_TYPE_DRAWING_AREA);
+
+static inline guchar
+shift_color_byte (guchar b,
+                  int    shift)
+{
+        return CLAMP(b + shift, 0, 255);
+}
+
+static void
+shift_colors (GdkPixbuf *pixbuf,
+              gint       red,
+              gint       green,
+              gint       blue,
+              gint       alpha)
+{
+        gint x, y, offset, y_offset, rowstride, width, height;
+        guchar *pixels;
+        gint channels;
+
+        width = gdk_pixbuf_get_width (pixbuf);
+        height = gdk_pixbuf_get_height (pixbuf);
+        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+        pixels = gdk_pixbuf_get_pixels (pixbuf);
+        channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+        for (y = 0; y < height; y++) {
+                y_offset = y * rowstride;
+                for (x = 0; x < width; x++) {
+                        offset = y_offset + x * channels;
+                        if (red != 0)
+                                pixels[offset] = shift_color_byte (pixels[offset], red);
+                        if (green != 0)
+                                pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
+                        if (blue != 0)
+                                pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
+                        if (alpha != 0 && channels >= 4)
+                                pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
+                }
+        }
+}
+
+static void
+update_pixbufs (UmCropArea *area)
+{
+        gint width;
+        gint height;
+        GtkAllocation allocation;
+        gdouble scale;
+        GdkRGBA color;
+        guint32 pixel;
+        gint dest_x, dest_y, dest_width, dest_height;
+        GtkWidget *widget;
+        GtkStyleContext *context;
+
+        widget = GTK_WIDGET (area);
+        gtk_widget_get_allocation (widget, &allocation);
+        context = gtk_widget_get_style_context (widget);
+
+        if (area->priv->pixbuf == NULL ||
+            gdk_pixbuf_get_width (area->priv->pixbuf) != allocation.width ||
+            gdk_pixbuf_get_height (area->priv->pixbuf) != allocation.height) {
+                if (area->priv->pixbuf != NULL)
+                        g_object_unref (area->priv->pixbuf);
+                area->priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                                     gdk_pixbuf_get_has_alpha (area->priv->browse_pixbuf),
+                                                     8,
+                                                     allocation.width, allocation.height);
+
+                gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &color);
+                pixel = (((gint)(color.red * 1.0)) << 16) |
+                        (((gint)(color.green * 1.0)) << 8) |
+                         ((gint)(color.blue * 1.0));
+                gdk_pixbuf_fill (area->priv->pixbuf, pixel);
+
+                width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+                height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+                scale = allocation.height / (gdouble)height;
+                if (scale * width > allocation.width)
+                    scale = allocation.width / (gdouble)width;
+
+                dest_width = width * scale;
+                dest_height = height * scale;
+                dest_x = (allocation.width - dest_width) / 2;
+                dest_y = (allocation.height - dest_height) / 2,
+
+                gdk_pixbuf_scale (area->priv->browse_pixbuf,
+                                  area->priv->pixbuf,
+                                  dest_x, dest_y,
+                                  dest_width, dest_height,
+                                  dest_x, dest_y,
+                                  scale, scale,
+                                  GDK_INTERP_BILINEAR);
+
+                if (area->priv->color_shifted)
+                        g_object_unref (area->priv->color_shifted);
+                area->priv->color_shifted = gdk_pixbuf_copy (area->priv->pixbuf);
+                shift_colors (area->priv->color_shifted, -32, -32, -32, 0);
+
+                if (area->priv->scale == 0.0) {
+                        area->priv->crop.width = 2 * area->priv->base_width / scale;
+                        area->priv->crop.height = 2 * area->priv->base_height / scale;
+                        area->priv->crop.x = (gdk_pixbuf_get_width (area->priv->browse_pixbuf) - area->priv->crop.width) / 2;
+                        area->priv->crop.y = (gdk_pixbuf_get_height (area->priv->browse_pixbuf) - area->priv->crop.height) / 2;
+                }
+
+                area->priv->scale = scale;
+                area->priv->image.x = dest_x;
+                area->priv->image.y = dest_y;
+                area->priv->image.width = dest_width;
+                area->priv->image.height = dest_height;
+        }
+}
+
+static void
+crop_to_widget (UmCropArea    *area,
+                GdkRectangle  *crop)
+{
+        crop->x = area->priv->image.x + area->priv->crop.x * area->priv->scale;
+        crop->y = area->priv->image.y + area->priv->crop.y * area->priv->scale;
+        crop->width = area->priv->crop.width * area->priv->scale;
+        crop->height = area->priv->crop.height * area->priv->scale;
+}
+
+typedef enum {
+        OUTSIDE,
+        INSIDE,
+        TOP,
+        TOP_LEFT,
+        TOP_RIGHT,
+        BOTTOM,
+        BOTTOM_LEFT,
+        BOTTOM_RIGHT,
+        LEFT,
+        RIGHT
+} Location;
+
+static gboolean
+um_crop_area_draw (GtkWidget *widget,
+                   cairo_t   *cr)
+{
+        GdkRectangle crop;
+        gint width, height;
+        UmCropArea *uarea = UM_CROP_AREA (widget);
+
+        if (uarea->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        update_pixbufs (uarea);
+
+        width = gdk_pixbuf_get_width (uarea->priv->pixbuf);
+        height = gdk_pixbuf_get_height (uarea->priv->pixbuf);
+        crop_to_widget (uarea, &crop);
+
+        gdk_cairo_set_source_pixbuf (cr, uarea->priv->color_shifted, 0, 0);
+        cairo_rectangle (cr, 0, 0, width, crop.y);
+        cairo_rectangle (cr, 0, crop.y, crop.x, crop.height);
+        cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.x - crop.width, crop.height);
+        cairo_rectangle (cr, 0, crop.y + crop.height, width, height - crop.y - crop.height);
+        cairo_fill (cr);
+
+        gdk_cairo_set_source_pixbuf (cr, uarea->priv->pixbuf, 0, 0);
+        cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height);
+        cairo_fill (cr);
+
+        if (uarea->priv->active_region != OUTSIDE) {
+                gint x1, x2, y1, y2;
+                cairo_set_source_rgb (cr, 1, 1, 1);
+                cairo_set_line_width (cr, 1.0);
+                x1 = crop.x + crop.width / 3.0;
+                x2 = crop.x + 2 * crop.width / 3.0;
+                y1 = crop.y + crop.height / 3.0;
+                y2 = crop.y + 2 * crop.height / 3.0;
+
+                cairo_move_to (cr, x1 + 0.5, crop.y);
+                cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
+
+                cairo_move_to (cr, x2 + 0.5, crop.y);
+                cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
+
+                cairo_move_to (cr, crop.x, y1 + 0.5);
+                cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
+
+                cairo_move_to (cr, crop.x, y2 + 0.5);
+                cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
+                cairo_stroke (cr);
+        }
+
+        cairo_set_source_rgb (cr,  0, 0, 0);
+        cairo_set_line_width (cr, 1.0);
+        cairo_rectangle (cr,
+                         crop.x + 0.5,
+                         crop.y + 0.5,
+                         crop.width - 1.0,
+                         crop.height - 1.0);
+        cairo_stroke (cr);
+
+        cairo_set_source_rgb (cr, 1, 1, 1);
+        cairo_set_line_width (cr, 2.0);
+        cairo_rectangle (cr,
+                         crop.x + 2.0,
+                         crop.y + 2.0,
+                         crop.width - 4.0,
+                         crop.height - 4.0);
+        cairo_stroke (cr);
+
+        return FALSE;
+}
+
+typedef enum {
+        BELOW,
+        LOWER,
+        BETWEEN,
+        UPPER,
+        ABOVE
+} Range;
+
+static Range
+find_range (gint x,
+            gint min,
+            gint max)
+{
+        gint tolerance = 12;
+
+        if (x < min - tolerance)
+                return BELOW;
+        if (x <= min + tolerance)
+                return LOWER;
+        if (x < max - tolerance)
+                return BETWEEN;
+        if (x <= max + tolerance)
+                return UPPER;
+        return ABOVE;
+}
+
+static Location
+find_location (GdkRectangle *rect,
+               gint          x,
+               gint          y)
+{
+        Range x_range, y_range;
+        Location location[5][5] = {
+                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE },
+                { OUTSIDE, TOP_LEFT,    TOP,     TOP_RIGHT,    OUTSIDE },
+                { OUTSIDE, LEFT,        INSIDE,  RIGHT,        OUTSIDE },
+                { OUTSIDE, BOTTOM_LEFT, BOTTOM,  BOTTOM_RIGHT, OUTSIDE },
+                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE }
+        };
+
+        x_range = find_range (x, rect->x, rect->x + rect->width);
+        y_range = find_range (y, rect->y, rect->y + rect->height);
+
+        return location[y_range][x_range];
+}
+
+static void
+update_cursor (UmCropArea *area,
+               gint           x,
+               gint           y)
+{
+        gint cursor_type;
+        GdkRectangle crop;
+        gint region;
+
+        region = area->priv->active_region;
+        if (region == OUTSIDE) {
+                crop_to_widget (area, &crop);
+                region = find_location (&crop, x, y);
+        }
+
+        switch (region) {
+        case OUTSIDE:
+                cursor_type = GDK_LEFT_PTR;
+                break;
+        case TOP_LEFT:
+                cursor_type = GDK_TOP_LEFT_CORNER;
+                break;
+        case TOP:
+                cursor_type = GDK_TOP_SIDE;
+                break;
+        case TOP_RIGHT:
+                cursor_type = GDK_TOP_RIGHT_CORNER;
+                break;
+        case LEFT:
+                cursor_type = GDK_LEFT_SIDE;
+                break;
+        case INSIDE:
+                cursor_type = GDK_FLEUR;
+                break;
+        case RIGHT:
+                cursor_type = GDK_RIGHT_SIDE;
+                break;
+        case BOTTOM_LEFT:
+                cursor_type = GDK_BOTTOM_LEFT_CORNER;
+                break;
+        case BOTTOM:
+                cursor_type = GDK_BOTTOM_SIDE;
+                break;
+        case BOTTOM_RIGHT:
+                cursor_type = GDK_BOTTOM_RIGHT_CORNER;
+                break;
+	default:
+		g_assert_not_reached ();
+        }
+
+        if (cursor_type != area->priv->current_cursor) {
+                GdkCursor *cursor = gdk_cursor_new (cursor_type);
+                gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
+                g_object_unref (cursor);
+                area->priv->current_cursor = cursor_type;
+        }
+}
+
+static int
+eval_radial_line (gdouble center_x, gdouble center_y,
+                  gdouble bounds_x, gdouble bounds_y,
+                  gdouble user_x)
+{
+        gdouble decision_slope;
+        gdouble decision_intercept;
+
+        decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
+        decision_intercept = -(decision_slope * bounds_x);
+
+        return (int) (decision_slope * user_x + decision_intercept);
+}
+
+static gboolean
+um_crop_area_motion_notify_event (GtkWidget      *widget,
+                                  GdkEventMotion *event)
+{
+        UmCropArea *area = UM_CROP_AREA (widget);
+        gint x, y;
+        gint delta_x, delta_y;
+        gint width, height;
+        gint adj_width, adj_height;
+        gint pb_width, pb_height;
+        GdkRectangle damage;
+        gint left, right, top, bottom;
+        gdouble new_width, new_height;
+        gdouble center_x, center_y;
+        gint min_width, min_height;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        update_cursor (area, event->x, event->y);
+
+        crop_to_widget (area, &damage);
+        gtk_widget_queue_draw_area (widget,
+                                    damage.x - 1, damage.y - 1,
+                                    damage.width + 2, damage.height + 2);
+
+        pb_width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+        pb_height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+        x = (event->x - area->priv->image.x) / area->priv->scale;
+        y = (event->y - area->priv->image.y) / area->priv->scale;
+
+        delta_x = x - area->priv->last_press_x;
+        delta_y = y - area->priv->last_press_y;
+        area->priv->last_press_x = x;
+        area->priv->last_press_y = y;
+
+        left = area->priv->crop.x;
+        right = area->priv->crop.x + area->priv->crop.width - 1;
+        top = area->priv->crop.y;
+        bottom = area->priv->crop.y + area->priv->crop.height - 1;
+
+        center_x = (left + right) / 2.0;
+        center_y = (top + bottom) / 2.0;
+
+        switch (area->priv->active_region) {
+        case INSIDE:
+                width = right - left + 1;
+                height = bottom - top + 1;
+
+                left += delta_x;
+                right += delta_x;
+                top += delta_y;
+                bottom += delta_y;
+
+                if (left < 0)
+                        left = 0;
+                if (top < 0)
+                        top = 0;
+                if (right > pb_width)
+                        right = pb_width;
+                if (bottom > pb_height)
+                        bottom = pb_height;
+
+                adj_width = right - left + 1;
+                adj_height = bottom - top + 1;
+                if (adj_width != width) {
+                        if (delta_x < 0)
+                                right = left + width - 1;
+                        else
+                                left = right - width + 1;
+                }
+                if (adj_height != height) {
+                        if (delta_y < 0)
+                                bottom = top + height - 1;
+                        else
+                                top = bottom - height + 1;
+                }
+
+                break;
+
+        case TOP_LEFT:
+                if (area->priv->aspect < 0) {
+                        top = y;
+                        left = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
+                        top = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        left = right - new_width;
+                }
+                else {
+                        left = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        top = bottom - new_height;
+                }
+                break;
+
+        case TOP:
+                top = y;
+                if (area->priv->aspect > 0) {
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                break;
+
+        case TOP_RIGHT:
+                if (area->priv->aspect < 0) {
+                        top = y;
+                        right = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
+                        top = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                else {
+                        right = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        top = bottom - new_height;
+                }
+                break;
+
+        case LEFT:
+                left = x;
+                if (area->priv->aspect > 0) {
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                break;
+
+        case BOTTOM_LEFT:
+                if (area->priv->aspect < 0) {
+                        bottom = y;
+                        left = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
+                        left = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                else {
+                        bottom = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        left = right - new_width;
+                }
+                break;
+
+        case RIGHT:
+                right = x;
+                if (area->priv->aspect > 0) {
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                break;
+
+        case BOTTOM_RIGHT:
+                if (area->priv->aspect < 0) {
+                        bottom = y;
+                        right = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
+                        right = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                else {
+                        bottom = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                break;
+
+        case BOTTOM:
+                bottom = y;
+                if (area->priv->aspect > 0) {
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right= left + new_width;
+                }
+                break;
+
+        default:
+                return FALSE;
+        }
+
+        min_width = area->priv->base_width / area->priv->scale;
+        min_height = area->priv->base_height / area->priv->scale;
+
+        width = right - left + 1;
+        height = bottom - top + 1;
+        if (area->priv->aspect < 0) {
+                if (left < 0)
+                        left = 0;
+                if (top < 0)
+                        top = 0;
+                if (right > pb_width)
+                        right = pb_width;
+                if (bottom > pb_height)
+                        bottom = pb_height;
+
+                width = right - left + 1;
+                height = bottom - top + 1;
+
+                switch (area->priv->active_region) {
+                case LEFT:
+                case TOP_LEFT:
+                case BOTTOM_LEFT:
+                        if (width < min_width)
+                                left = right - min_width;
+                        break;
+                case RIGHT:
+                case TOP_RIGHT:
+                case BOTTOM_RIGHT:
+                        if (width < min_width)
+                                right = left + min_width;
+                        break;
+
+                default: ;
+                }
+
+                switch (area->priv->active_region) {
+                case TOP:
+                case TOP_LEFT:
+                case TOP_RIGHT:
+                        if (height < min_height)
+                                top = bottom - min_height;
+                        break;
+                case BOTTOM:
+                case BOTTOM_LEFT:
+                case BOTTOM_RIGHT:
+                        if (height < min_height)
+                                bottom = top + min_height;
+                        break;
+
+                default: ;
+                }
+        }
+        else {
+                if (left < 0 || top < 0 ||
+                    right > pb_width || bottom > pb_height ||
+                    width < min_width || height < min_height) {
+                        left = area->priv->crop.x;
+                        right = area->priv->crop.x + area->priv->crop.width - 1;
+                        top = area->priv->crop.y;
+                        bottom = area->priv->crop.y + area->priv->crop.height - 1;
+                }
+        }
+
+        area->priv->crop.x = left;
+        area->priv->crop.y = top;
+        area->priv->crop.width = right - left + 1;
+        area->priv->crop.height = bottom - top + 1;
+
+        crop_to_widget (area, &damage);
+        gtk_widget_queue_draw_area (widget,
+                                    damage.x - 1, damage.y - 1,
+                                    damage.width + 2, damage.height + 2);
+
+        return FALSE;
+}
+
+static gboolean
+um_crop_area_button_press_event (GtkWidget      *widget,
+                                 GdkEventButton *event)
+{
+        UmCropArea *area = UM_CROP_AREA (widget);
+        GdkRectangle crop;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        crop_to_widget (area, &crop);
+
+        area->priv->last_press_x = (event->x - area->priv->image.x) / area->priv->scale;
+        area->priv->last_press_y = (event->y - area->priv->image.y) / area->priv->scale;
+        area->priv->active_region = find_location (&crop, event->x, event->y);
+
+        gtk_widget_queue_draw_area (widget,
+                                    crop.x - 1, crop.y - 1,
+                                    crop.width + 2, crop.height + 2);
+
+        return FALSE;
+}
+
+static gboolean
+um_crop_area_button_release_event (GtkWidget      *widget,
+                                   GdkEventButton *event)
+{
+        UmCropArea *area = UM_CROP_AREA (widget);
+        GdkRectangle crop;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        crop_to_widget (area, &crop);
+
+        area->priv->last_press_x = -1;
+        area->priv->last_press_y = -1;
+        area->priv->active_region = OUTSIDE;
+
+        gtk_widget_queue_draw_area (widget,
+                                    crop.x - 1, crop.y - 1,
+                                    crop.width + 2, crop.height + 2);
+
+        return FALSE;
+}
+
+static void
+um_crop_area_finalize (GObject *object)
+{
+        UmCropArea *area = UM_CROP_AREA (object);
+
+        if (area->priv->browse_pixbuf) {
+                g_object_unref (area->priv->browse_pixbuf);
+                area->priv->browse_pixbuf = NULL;
+        }
+        if (area->priv->pixbuf) {
+                g_object_unref (area->priv->pixbuf);
+                area->priv->pixbuf = NULL;
+        }
+        if (area->priv->color_shifted) {
+                g_object_unref (area->priv->color_shifted);
+                area->priv->color_shifted = NULL;
+        }
+}
+
+static void
+um_crop_area_class_init (UmCropAreaClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->finalize = um_crop_area_finalize;
+        widget_class->draw = um_crop_area_draw;
+        widget_class->button_press_event = um_crop_area_button_press_event;
+        widget_class->button_release_event = um_crop_area_button_release_event;
+        widget_class->motion_notify_event = um_crop_area_motion_notify_event;
+
+        g_type_class_add_private (klass, sizeof (UmCropAreaPrivate));
+}
+
+static void
+um_crop_area_init (UmCropArea *area)
+{
+        area->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((area), UM_TYPE_CROP_AREA,
+                                                   UmCropAreaPrivate));
+
+        gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
+                               GDK_BUTTON_PRESS_MASK |
+                               GDK_BUTTON_RELEASE_MASK);
+
+        area->priv->scale = 0.0;
+        area->priv->image.x = 0;
+        area->priv->image.y = 0;
+        area->priv->image.width = 0;
+        area->priv->image.height = 0;
+        area->priv->active_region = OUTSIDE;
+        area->priv->base_width = 48;
+        area->priv->base_height = 48;
+        area->priv->aspect = 1;
+}
+
+GtkWidget *
+um_crop_area_new (void)
+{
+        return g_object_new (UM_TYPE_CROP_AREA, NULL);
+}
+
+GdkPixbuf *
+um_crop_area_get_picture (UmCropArea *area)
+{
+        gint width, height;
+
+        width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+        height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+        width = MIN (area->priv->crop.width, width - area->priv->crop.x);
+        height = MIN (area->priv->crop.height, height - area->priv->crop.y);
+
+        return gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
+                                         area->priv->crop.x,
+                                         area->priv->crop.y,
+                                         width, height);
+}
+
+void
+um_crop_area_set_picture (UmCropArea *area,
+                          GdkPixbuf  *pixbuf)
+{
+        int width;
+        int height;
+
+        if (area->priv->browse_pixbuf) {
+                g_object_unref (area->priv->browse_pixbuf);
+                area->priv->browse_pixbuf = NULL;
+        }
+        if (pixbuf) {
+                area->priv->browse_pixbuf = g_object_ref (pixbuf);
+                width = gdk_pixbuf_get_width (pixbuf);
+                height = gdk_pixbuf_get_height (pixbuf);
+        } else {
+                width = 0;
+                height = 0;
+        }
+
+        area->priv->crop.width = 2 * area->priv->base_width;
+        area->priv->crop.height = 2 * area->priv->base_height;
+        area->priv->crop.x = (width - area->priv->crop.width) / 2;
+        area->priv->crop.y = (height - area->priv->crop.height) / 2;
+
+        area->priv->scale = 0.0;
+        area->priv->image.x = 0;
+        area->priv->image.y = 0;
+        area->priv->image.width = 0;
+        area->priv->image.height = 0;
+
+        gtk_widget_queue_draw (GTK_WIDGET (area));
+}
+
+void
+um_crop_area_set_min_size (UmCropArea *area,
+                           gint        width,
+                           gint        height)
+{
+        area->priv->base_width = width;
+        area->priv->base_height = height;
+
+        if (area->priv->aspect > 0) {
+                area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+        }
+}
+
+void
+um_crop_area_set_constrain_aspect (UmCropArea *area,
+                                   gboolean    constrain)
+{
+        if (constrain) {
+                area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+        }
+        else {
+                area->priv->aspect = -1;
+        }
+}
+
diff --git a/gui/initial-setup/um-crop-area.h b/gui/initial-setup/um-crop-area.h
new file mode 100644
index 0000000..8992957
--- /dev/null
+++ b/gui/initial-setup/um-crop-area.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright  2009 Bastien Nocera <hadess hadess net>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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/>.
+ */
+
+#ifndef _UM_CROP_AREA_H_
+#define _UM_CROP_AREA_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_CROP_AREA (um_crop_area_get_type ())
+#define UM_CROP_AREA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_CROP_AREA, \
+                                                                           UmCropArea))
+#define UM_CROP_AREA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_CROP_AREA, \
+                                                                        UmCropAreaClass))
+#define UM_IS_CROP_AREA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_CROP_AREA))
+#define UM_IS_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_CROP_AREA))
+#define UM_CROP_AREA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_CROP_AREA, \
+                                                                          UmCropAreaClass))
+
+typedef struct _UmCropAreaClass UmCropAreaClass;
+typedef struct _UmCropArea UmCropArea;
+typedef struct _UmCropAreaPrivate UmCropAreaPrivate;
+
+struct _UmCropAreaClass {
+        GtkDrawingAreaClass parent_class;
+};
+
+struct _UmCropArea {
+        GtkDrawingArea parent_instance;
+        UmCropAreaPrivate *priv;
+};
+
+GType      um_crop_area_get_type             (void) G_GNUC_CONST;
+
+GtkWidget *um_crop_area_new                  (void);
+GdkPixbuf *um_crop_area_get_picture          (UmCropArea *area);
+void       um_crop_area_set_picture          (UmCropArea *area,
+                                              GdkPixbuf  *pixbuf);
+void       um_crop_area_set_min_size         (UmCropArea *area,
+                                              gint        width,
+                                              gint        height);
+void       um_crop_area_set_constrain_aspect (UmCropArea *area,
+                                              gboolean    constrain);
+
+G_END_DECLS
+
+#endif /* _UM_CROP_AREA_H_ */
diff --git a/gui/initial-setup/um-photo-dialog.c b/gui/initial-setup/um-photo-dialog.c
new file mode 100644
index 0000000..e95c6fc
--- /dev/null
+++ b/gui/initial-setup/um-photo-dialog.c
@@ -0,0 +1,599 @@
+/* -*- 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 3 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.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 "um-photo-dialog.h"
+#include "um-crop-area.h"
+#include "um-utils.h"
+
+#define ROW_SPAN 6
+
+struct _UmPhotoDialog {
+        GtkWidget *photo_popup;
+        GtkWidget *popup_button;
+        GtkWidget *crop_area;
+
+#ifdef HAVE_CHEESE
+        CheeseCameraDeviceMonitor *monitor;
+        GtkWidget *take_photo_menuitem;
+        guint num_cameras;
+#endif /* HAVE_CHEESE */
+
+        GnomeDesktopThumbnailFactory *thumb_factory;
+
+        SelectAvatarCallback *callback;
+        gpointer              data;
+};
+
+static void
+crop_dialog_response (GtkWidget     *dialog,
+                      gint           response_id,
+                      UmPhotoDialog *um)
+{
+        GdkPixbuf *pb, *pb2;
+
+        if (response_id != GTK_RESPONSE_ACCEPT) {
+                um->crop_area = NULL;
+                gtk_widget_destroy (dialog);
+                return;
+        }
+
+        pb = um_crop_area_get_picture (UM_CROP_AREA (um->crop_area));
+        pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+        um->callback (pb2, NULL, um->data);
+
+        g_object_unref (pb2);
+        g_object_unref (pb);
+
+        um->crop_area = NULL;
+        gtk_widget_destroy (dialog);
+}
+
+static void
+um_photo_dialog_crop (UmPhotoDialog *um,
+                      GdkPixbuf     *pixbuf)
+{
+        GtkWidget *dialog;
+        GtkWidget *frame;
+
+        dialog = gtk_dialog_new_with_buttons ("",
+                                              GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+                                              0,
+                                              GTK_STOCK_CANCEL,
+                                              GTK_RESPONSE_REJECT,
+                                              _("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 (G_OBJECT (dialog), "response",
+                          G_CALLBACK (crop_dialog_response), um);
+
+        /* Content */
+        um->crop_area           = um_crop_area_new ();
+        um_crop_area_set_min_size (UM_CROP_AREA (um->crop_area), 48, 48);
+        um_crop_area_set_constrain_aspect (UM_CROP_AREA (um->crop_area), TRUE);
+        um_crop_area_set_picture (UM_CROP_AREA (um->crop_area), pixbuf);
+        frame                   = gtk_frame_new (NULL);
+        gtk_container_add (GTK_CONTAINER (frame), um->crop_area);
+        gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+
+        gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+                            frame,
+                            TRUE, TRUE, 8);
+
+        gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
+
+        gtk_widget_show_all (dialog);
+}
+
+static void
+file_chooser_response (GtkDialog     *chooser,
+                       gint           response,
+                       UmPhotoDialog *um)
+{
+        gchar *filename;
+        GError *error;
+        GdkPixbuf *pixbuf;
+
+        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);
+
+        gtk_widget_destroy (GTK_WIDGET (chooser));
+
+        um_photo_dialog_crop (um, pixbuf);
+        g_object_unref (pixbuf);
+}
+
+static void
+update_preview (GtkFileChooser               *chooser,
+                GnomeDesktopThumbnailFactory *thumb_factory)
+{
+        gchar *uri;
+
+        uri = gtk_file_chooser_get_preview_uri (chooser);
+
+        if (uri) {
+                GdkPixbuf *pixbuf = NULL;
+                const gchar *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,
+                                               G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                                               G_FILE_QUERY_INFO_NONE,
+                                               NULL, NULL);
+                g_object_unref (file);
+
+                if (file_info != NULL) {
+                        mime_type = 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);
+                }
+
+                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_stock (GTK_IMAGE (preview),
+                                                  GTK_STOCK_DIALOG_QUESTION,
+                                                  GTK_ICON_SIZE_DIALOG);
+                }
+
+                g_free (uri);
+        }
+
+        gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+}
+
+static void
+um_photo_dialog_select_file (UmPhotoDialog *um)
+{
+        GtkWidget *chooser;
+        const gchar *folder;
+        GtkWidget *preview;
+
+        chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"),
+                                               GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+                                               GTK_FILE_CHOOSER_ACTION_OPEN,
+                                               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                               GTK_STOCK_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);
+        g_signal_connect (chooser, "update-preview",
+                          G_CALLBACK (update_preview), um->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);
+
+        g_signal_connect (chooser, "response",
+                          G_CALLBACK (file_chooser_response), um);
+
+        gtk_window_present (GTK_WINDOW (chooser));
+}
+
+static void
+none_icon_selected (GtkMenuItem   *menuitem,
+                    UmPhotoDialog *um)
+{
+        um->callback (NULL, NULL, um->data);
+}
+
+static void
+file_icon_selected (GtkMenuItem   *menuitem,
+                    UmPhotoDialog *um)
+{
+        um_photo_dialog_select_file (um);
+}
+
+#ifdef HAVE_CHEESE
+static gboolean
+destroy_chooser (GtkWidget *chooser)
+{
+        gtk_widget_destroy (chooser);
+        return FALSE;
+}
+
+static void
+webcam_response_cb (GtkDialog     *dialog,
+                    int            response,
+                    UmPhotoDialog  *um)
+{
+        if (response == GTK_RESPONSE_ACCEPT) {
+                GdkPixbuf *pb, *pb2;
+
+                g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL);
+                pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+                um->callback (pb2, NULL, um->data);
+
+                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);
+}
+
+static void
+webcam_icon_selected (GtkMenuItem   *menuitem,
+                      UmPhotoDialog *um)
+{
+        GtkWidget *window;
+
+        window = cheese_avatar_chooser_new ();
+        gtk_window_set_transient_for (GTK_WINDOW (window),
+                                      GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)));
+        gtk_window_set_modal (GTK_WINDOW (window), TRUE);
+        g_signal_connect (G_OBJECT (window), "response",
+                          G_CALLBACK (webcam_response_cb), um);
+        gtk_widget_show (window);
+}
+
+static void
+update_photo_menu_status (UmPhotoDialog *um)
+{
+        if (um->num_cameras == 0)
+                gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+        else
+                gtk_widget_set_sensitive (um->take_photo_menuitem, TRUE);
+}
+
+static void
+device_added (CheeseCameraDeviceMonitor *monitor,
+              CheeseCameraDevice        *device,
+              UmPhotoDialog             *um)
+{
+        um->num_cameras++;
+        update_photo_menu_status (um);
+}
+
+static void
+device_removed (CheeseCameraDeviceMonitor *monitor,
+                const char                *id,
+                UmPhotoDialog             *um)
+{
+        um->num_cameras--;
+        update_photo_menu_status (um);
+}
+
+#endif /* HAVE_CHEESE */
+
+static void
+stock_icon_selected (GtkMenuItem   *menuitem,
+                     UmPhotoDialog *um)
+{
+        const char *filename;
+
+        filename = g_object_get_data (G_OBJECT (menuitem), "filename");
+        um->callback (NULL, filename, um->data);
+}
+
+static GtkWidget *
+menu_item_for_filename (UmPhotoDialog *um,
+                        const char    *filename)
+{
+        GtkWidget *image, *menuitem;
+        GFile *file;
+        GIcon *icon;
+
+        file = g_file_new_for_path (filename);
+        icon = g_file_icon_new (file);
+        g_object_unref (file);
+        image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+        g_object_unref (icon);
+
+        menuitem = gtk_menu_item_new ();
+        gtk_container_add (GTK_CONTAINER (menuitem), image);
+        gtk_widget_show_all (menuitem);
+
+        g_object_set_data_full (G_OBJECT (menuitem), "filename",
+                                g_strdup (filename), (GDestroyNotify) g_free);
+        g_signal_connect (G_OBJECT (menuitem), "activate",
+                          G_CALLBACK (stock_icon_selected), um);
+
+        return menuitem;
+}
+
+static void
+setup_photo_popup (UmPhotoDialog *um)
+{
+        GtkWidget *menu, *menuitem, *image;
+        guint x, y;
+        const gchar * const * dirs;
+        guint i;
+        GDir *dir;
+        const char *face;
+        gboolean none_item_shown;
+        gboolean added_faces;
+
+        menu = gtk_menu_new ();
+
+        x = 0;
+        y = 0;
+        none_item_shown = added_faces = FALSE;
+
+        dirs = g_get_system_data_dirs ();
+        for (i = 0; dirs[i] != NULL; i++) {
+                char *path;
+
+                path = g_build_filename (dirs[i], "pixmaps", "faces", NULL);
+                dir = g_dir_open (path, 0, NULL);
+                if (dir == NULL) {
+                        g_free (path);
+                        continue;
+                }
+
+                while ((face = g_dir_read_name (dir)) != NULL) {
+                        char *filename;
+
+                        added_faces = TRUE;
+
+                        filename = g_build_filename (path, face, NULL);
+                        menuitem = menu_item_for_filename (um, filename);
+                        g_free (filename);
+                        if (menuitem == NULL)
+                                continue;
+
+                        gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+                                         x, x + 1, y, y + 1);
+                        gtk_widget_show (menuitem);
+
+                        x++;
+                        if (x >= ROW_SPAN - 1) {
+                                y++;
+                                x = 0;
+                        }
+                }
+                g_dir_close (dir);
+                g_free (path);
+
+                if (added_faces)
+                        break;
+        }
+
+        if (!added_faces)
+                goto skip_faces;
+
+        image = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_DIALOG);
+        menuitem = gtk_menu_item_new ();
+        gtk_container_add (GTK_CONTAINER (menuitem), image);
+        gtk_widget_show_all (menuitem);
+        gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+                         x, x + 1, y, y + 1);
+        g_signal_connect (G_OBJECT (menuitem), "activate",
+                          G_CALLBACK (none_icon_selected), um);
+        gtk_widget_show (menuitem);
+        none_item_shown = TRUE;
+        y++;
+
+skip_faces:
+        if (!none_item_shown) {
+                menuitem = gtk_menu_item_new_with_label (_("Disable image"));
+                gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+                                 0, ROW_SPAN - 1, y, y + 1);
+                g_signal_connect (G_OBJECT (menuitem), "activate",
+                                  G_CALLBACK (none_icon_selected), um);
+                gtk_widget_show (menuitem);
+                y++;
+        }
+
+        /* Separator */
+        menuitem = gtk_separator_menu_item_new ();
+        gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+                         0, ROW_SPAN - 1, y, y + 1);
+        gtk_widget_show (menuitem);
+
+        y++;
+
+#ifdef HAVE_CHEESE
+        um->take_photo_menuitem = gtk_menu_item_new_with_label (_("Take a photo..."));
+        gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (um->take_photo_menuitem),
+                         0, ROW_SPAN - 1, y, y + 1);
+        g_signal_connect (G_OBJECT (um->take_photo_menuitem), "activate",
+                          G_CALLBACK (webcam_icon_selected), um);
+        gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+        gtk_widget_show (um->take_photo_menuitem);
+
+        um->monitor = cheese_camera_device_monitor_new ();
+        g_signal_connect (G_OBJECT (um->monitor), "added",
+                          G_CALLBACK (device_added), um);
+        g_signal_connect (G_OBJECT (um->monitor), "removed",
+                          G_CALLBACK (device_removed), um);
+        cheese_camera_device_monitor_coldplug (um->monitor);
+
+        y++;
+#endif /* HAVE_CHEESE */
+
+        menuitem = gtk_menu_item_new_with_label (_("Browse for more pictures..."));
+        gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+                         0, ROW_SPAN - 1, y, y + 1);
+        g_signal_connect (G_OBJECT (menuitem), "activate",
+                          G_CALLBACK (file_icon_selected), um);
+        gtk_widget_show (menuitem);
+
+        um->photo_popup = menu;
+}
+
+static void
+popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um)
+{
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) && !gtk_widget_get_visible (um->photo_popup)) {
+                gtk_menu_popup (GTK_MENU (um->photo_popup),
+                                NULL, NULL,
+                                (GtkMenuPositionFunc) popup_menu_below_button, um->popup_button,
+                                0, gtk_get_current_event_time ());
+        } else {
+                gtk_menu_popdown (GTK_MENU (um->photo_popup));
+        }
+}
+
+static gboolean
+on_popup_button_button_pressed (GtkToggleButton *button,
+                                GdkEventButton *event,
+                                UmPhotoDialog  *um)
+{
+        if (event->button == 1) {
+                if (!gtk_widget_get_visible (um->photo_popup)) {
+                        popup_icon_menu (button, um);
+                        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+                } else {
+                        gtk_menu_popdown (GTK_MENU (um->photo_popup));
+                        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+                }
+
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static void
+on_photo_popup_unmap (GtkWidget     *popup_menu,
+                      UmPhotoDialog *um)
+{
+        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->popup_button), FALSE);
+}
+
+static void
+popup_button_draw (GtkWidget      *widget,
+                   cairo_t        *cr,
+                   UmPhotoDialog  *um)
+{
+        if (gtk_widget_get_state (gtk_bin_get_child (GTK_BIN (widget))) != GTK_STATE_PRELIGHT &&
+            !gtk_widget_is_focus (widget)) {
+                return;
+        }
+
+        down_arrow (gtk_widget_get_style_context (widget),
+                    cr,
+                    gtk_widget_get_allocated_width (widget) - 12,
+                    gtk_widget_get_allocated_height (widget) - 12,
+                    12, 12);
+}
+
+static void
+popup_button_focus_changed (GObject       *button,
+                            GParamSpec    *pspec,
+                            UmPhotoDialog *um)
+{
+        gtk_widget_queue_draw (gtk_bin_get_child (GTK_BIN (button)));
+}
+
+UmPhotoDialog *
+um_photo_dialog_new (GtkWidget            *button,
+                     SelectAvatarCallback  callback,
+                     gpointer              data)
+{
+        UmPhotoDialog *um;
+
+        um = g_new0 (UmPhotoDialog, 1);
+
+        um->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+
+        /* Set up the popup */
+        um->popup_button = button;
+        setup_photo_popup (um);
+        g_signal_connect (button, "toggled",
+                          G_CALLBACK (popup_icon_menu), um);
+        g_signal_connect (button, "button-press-event",
+                          G_CALLBACK (on_popup_button_button_pressed), um);
+        g_signal_connect (button, "notify::is-focus",
+                          G_CALLBACK (popup_button_focus_changed), um);
+        g_signal_connect_after (button, "draw",
+                                G_CALLBACK (popup_button_draw), um);
+
+        g_signal_connect (um->photo_popup, "unmap",
+                          G_CALLBACK (on_photo_popup_unmap), um);
+
+        um->callback = callback;
+        um->data = data;
+
+        return um;
+}
+
+void
+um_photo_dialog_free (UmPhotoDialog *um)
+{
+        gtk_widget_destroy (um->photo_popup);
+
+        if (um->thumb_factory)
+                g_object_unref (um->thumb_factory);
+#ifdef HAVE_CHEESE
+        if (um->monitor)
+                g_object_unref (um->monitor);
+#endif
+
+        g_free (um);
+}
diff --git a/gui/initial-setup/um-photo-dialog.h b/gui/initial-setup/um-photo-dialog.h
new file mode 100644
index 0000000..697fe30
--- /dev/null
+++ b/gui/initial-setup/um-photo-dialog.h
@@ -0,0 +1,41 @@
+/* -*- 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 3 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __UM_PHOTO_DIALOG_H__
+#define __UM_PHOTO_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _UmPhotoDialog UmPhotoDialog;
+typedef void (SelectAvatarCallback) (GdkPixbuf   *pixbuf,
+                                     const gchar *filename,
+                                     gpointer     data);
+
+UmPhotoDialog *um_photo_dialog_new      (GtkWidget            *button,
+                                         SelectAvatarCallback  callback,
+                                         gpointer              data);
+void           um_photo_dialog_free     (UmPhotoDialog *dialog);
+
+G_END_DECLS
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]