diff options
author | Matthias Clasen <mclasen@redhat.com> | 2012-05-07 20:54:36 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2012-05-07 22:46:39 -0400 |
commit | 827552bbd814a6755dd799b5c27df3f5bed5847b (patch) | |
tree | 2ac2677c1a4643e6ed83917257aa50fcedace435 | |
parent | ab09560ec59f42f25e480e2b4433fc204064c1c3 (diff) | |
download | gdm-827552bbd814a6755dd799b5c27df3f5bed5847b.tar.gz |
initial-setup: add an avatar chooser
This code is adapted from the user panel.
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | gui/initial-setup/Makefile.am | 3 | ||||
-rw-r--r-- | gui/initial-setup/gdm-initial-setup.c | 98 | ||||
-rw-r--r-- | gui/initial-setup/setup.ui | 18 | ||||
-rw-r--r-- | gui/initial-setup/um-crop-area.c | 818 | ||||
-rw-r--r-- | gui/initial-setup/um-crop-area.h | 65 | ||||
-rw-r--r-- | gui/initial-setup/um-photo-dialog.c | 599 | ||||
-rw-r--r-- | gui/initial-setup/um-photo-dialog.h | 41 |
8 files changed, 1648 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index fdc298e3..39d76e48 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 d9640575..e73c0b80 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 1f67b6ca..b86ece7c 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 6fb3d8f5..2fdccaeb 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 00000000..94ddd0d9 --- /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 00000000..89929577 --- /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 00000000..e95c6fc9 --- /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 00000000..697fe303 --- /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 |