/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright 2009-2010 Red Hat, Inc, * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * Written by: Matthias Clasen */ #include "config.h" #include #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #ifdef HAVE_CHEESE #include #include #include #endif /* HAVE_CHEESE */ #include "um-photo-dialog.h" #include "cc-crop-area.h" #include "um-utils.h" #define ROW_SPAN 5 #define AVATAR_PIXEL_SIZE 72 struct _UmPhotoDialog { GtkPopover parent; GtkWidget *popup_button; GtkWidget *crop_area; GtkWidget *flowbox; GtkWidget *take_picture_button; #ifdef HAVE_CHEESE CheeseCameraDeviceMonitor *monitor; GCancellable *cancellable; guint num_cameras; #endif /* HAVE_CHEESE */ GnomeDesktopThumbnailFactory *thumb_factory; GListStore *faces; ActUser *user; }; G_DEFINE_TYPE (UmPhotoDialog, um_photo_dialog, GTK_TYPE_POPOVER) 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 = cc_crop_area_get_picture (CC_CROP_AREA (um->crop_area)); pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR); set_user_icon_data (um->user, pb2); g_object_unref (pb2); g_object_unref (pb); um->crop_area = NULL; gtk_widget_destroy (dialog); gtk_popover_popdown (GTK_POPOVER (um)); } static void um_photo_dialog_crop (UmPhotoDialog *um, GdkPixbuf *pixbuf) { GtkWidget *dialog; dialog = gtk_dialog_new_with_buttons ("", GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)), GTK_DIALOG_USE_HEADER_BAR, _("_Cancel"), GTK_RESPONSE_CANCEL, _("Select"), GTK_RESPONSE_ACCEPT, NULL); gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users"); g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (crop_dialog_response), um); /* Content */ um->crop_area = cc_crop_area_new (); cc_crop_area_set_min_size (CC_CROP_AREA (um->crop_area), 48, 48); cc_crop_area_set_constrain_aspect (CC_CROP_AREA (um->crop_area), TRUE); cc_crop_area_set_picture (CC_CROP_AREA (um->crop_area), pixbuf); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), um->crop_area, 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, *pixbuf2; if (response != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy (GTK_WIDGET (chooser)); return; } filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser)); error = NULL; pixbuf = gdk_pixbuf_new_from_file (filename, &error); if (pixbuf == NULL) { g_warning ("Failed to load %s: %s", filename, error->message); g_error_free (error); } g_free (filename); pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf); g_object_unref (pixbuf); gtk_widget_destroy (GTK_WIDGET (chooser)); um_photo_dialog_crop (um, pixbuf2); g_object_unref (pixbuf2); } static void update_preview (GtkFileChooser *chooser, GnomeDesktopThumbnailFactory *thumb_factory) { gchar *uri; uri = gtk_file_chooser_get_uri (chooser); if (uri) { GdkPixbuf *pixbuf = NULL; char *mime_type = NULL; GFile *file; GFileInfo *file_info; GtkWidget *preview; preview = gtk_file_chooser_get_preview_widget (chooser); file = g_file_new_for_uri (uri); file_info = g_file_query_info (file, "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL); g_object_unref (file); if (file_info != NULL && g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) { mime_type = g_strdup (g_file_info_get_content_type (file_info)); g_object_unref (file_info); } if (mime_type) { pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumb_factory, uri, mime_type); g_free (mime_type); } gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT, (pixbuf != NULL)); if (pixbuf != NULL) { gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf); g_object_unref (pixbuf); } else { gtk_image_set_from_icon_name (GTK_IMAGE (preview), "dialog-question", GTK_ICON_SIZE_DIALOG); } g_free (uri); } gtk_file_chooser_set_preview_widget_active (chooser, TRUE); } static void um_photo_dialog_select_file (UmPhotoDialog *um) { GtkWidget *chooser; const gchar *folder; GtkWidget *preview; GtkFileFilter *filter; chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"), GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL); gtk_window_set_modal (GTK_WINDOW (chooser), TRUE); preview = gtk_image_new (); gtk_widget_set_size_request (preview, 128, -1); gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (chooser), preview); gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (chooser), FALSE); gtk_widget_show (preview); /* Preview has to be generated after default handler of "selection-changed" * signal, otherwise dialog response sensitivity is rewritten (Bug 547988). * Preview also has to be generated on "selection-changed" signal to reflect * all changes (Bug 660877). */ g_signal_connect_after (chooser, "selection-changed", G_CALLBACK (update_preview), 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); filter = gtk_file_filter_new (); gtk_file_filter_add_pixbuf_formats (filter); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), filter); g_signal_connect (chooser, "response", G_CALLBACK (file_chooser_response), um); gtk_window_present (GTK_WINDOW (chooser)); } #ifdef HAVE_CHEESE static gboolean destroy_chooser (GtkWidget *chooser) { gtk_widget_destroy (chooser); return FALSE; } static void webcam_response_cb (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); set_user_icon_data (um->user, pb2); g_object_unref (pb2); g_object_unref (pb); } if (response != GTK_RESPONSE_DELETE_EVENT && response != GTK_RESPONSE_NONE) g_idle_add ((GSourceFunc) destroy_chooser, dialog); gtk_popover_popdown (GTK_POPOVER (um)); } static void webcam_icon_selected (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_visible (um->take_picture_button, FALSE); else gtk_widget_set_sensitive (um->take_picture_button, 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 face_widget_activated (GtkFlowBox *flowbox, GtkFlowBoxChild *child, UmPhotoDialog *um) { const gchar *filename; GtkWidget *image; image = gtk_bin_get_child (GTK_BIN (child)); filename = g_object_get_data (G_OBJECT (image), "filename"); act_user_set_icon_file (um->user, filename); gtk_popover_popdown (GTK_POPOVER (um)); } static GtkWidget * create_face_widget (gpointer item, gpointer user_data) { GtkWidget *image; GIcon *icon; icon = g_file_icon_new (G_FILE (item)); image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG); gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_PIXEL_SIZE); g_object_unref (icon); gtk_widget_show (image); g_object_set_data (G_OBJECT (image), "filename", g_file_get_path (G_FILE (item))); return image; } #ifdef HAVE_CHEESE static void setup_cheese_camera_device_monitor (UmPhotoDialog *um) { 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); } static void cheese_camera_device_monitor_new_cb (GObject *source, GAsyncResult *result, gpointer user_data) { UmPhotoDialog *um = user_data; GObject *ret; ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, NULL); if (ret == NULL) return; um->monitor = CHEESE_CAMERA_DEVICE_MONITOR (ret); setup_cheese_camera_device_monitor (um); } #endif /* HAVE_CHEESE */ static void setup_photo_popup (UmPhotoDialog *um) { GFile *file, *dir; GFileInfo *info; GFileEnumerator *enumerator; GFileType type; const gchar *target; const gchar * const * dirs; guint i; gboolean added_faces; um->faces = g_list_store_new (G_TYPE_FILE); gtk_flow_box_bind_model (GTK_FLOW_BOX (um->flowbox), G_LIST_MODEL (um->faces), create_face_widget, um, NULL); g_signal_connect (um->flowbox, "child-activated", G_CALLBACK (face_widget_activated), um); 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_file_new_for_path (path); g_free (path); enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enumerator == NULL) { g_object_unref (dir); continue; } while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { added_faces = TRUE; type = g_file_info_get_file_type (info); if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_SYMBOLIC_LINK) { g_object_unref (info); continue; } target = g_file_info_get_symlink_target (info); if (target != NULL && g_str_has_prefix (target , "legacy/")) { g_object_unref (info); continue; } file = g_file_get_child (dir, g_file_info_get_name (info)); g_list_store_append (um->faces, file); g_object_unref (info); } g_file_enumerator_close (enumerator, NULL, NULL); g_object_unref (enumerator); g_object_unref (dir); if (added_faces) break; } #ifdef HAVE_CHEESE gtk_widget_set_visible (um->take_picture_button, TRUE); um->cancellable = g_cancellable_new (); g_async_initable_new_async (CHEESE_TYPE_CAMERA_DEVICE_MONITOR, G_PRIORITY_DEFAULT, um->cancellable, cheese_camera_device_monitor_new_cb, um, NULL); #endif /* HAVE_CHEESE */ } static void popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um) { gtk_popover_popup (GTK_POPOVER (um)); } static gboolean on_popup_button_button_pressed (GtkToggleButton *button, GdkEventButton *event, UmPhotoDialog *um) { if (event->button == 1) { if (!gtk_widget_get_visible (GTK_WIDGET (um))) { popup_icon_menu (button, um); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); } else { gtk_popover_popdown (GTK_POPOVER (um)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE); } return TRUE; } return FALSE; } UmPhotoDialog * um_photo_dialog_new (GtkWidget *button) { UmPhotoDialog *um; um = g_object_new (UM_TYPE_PHOTO_DIALOG, "relative-to", button, NULL); 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); return um; } static void um_photo_dialog_dispose (GObject *object) { UmPhotoDialog *um = UM_PHOTO_DIALOG (object); g_clear_object (&um->thumb_factory); #ifdef HAVE_CHEESE g_cancellable_cancel (um->cancellable); g_clear_object (&um->cancellable); g_clear_object (&um->monitor); #endif g_clear_object (&um->user); G_OBJECT_CLASS (um_photo_dialog_parent_class)->dispose (object); } static void um_photo_dialog_init (UmPhotoDialog *um) { gtk_widget_init_template (GTK_WIDGET (um)); } static void um_photo_dialog_class_init (UmPhotoDialogClass *klass) { GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass); GObjectClass *oclass = G_OBJECT_CLASS (klass); gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/control-center/user-accounts/avatar-chooser.ui"); gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, flowbox); gtk_widget_class_bind_template_child (wclass, UmPhotoDialog, take_picture_button); gtk_widget_class_bind_template_callback (wclass, um_photo_dialog_select_file); #ifdef HAVE_CHEESE gtk_widget_class_bind_template_callback (wclass, webcam_icon_selected); #endif oclass->dispose = um_photo_dialog_dispose; } void um_photo_dialog_set_user (UmPhotoDialog *um, ActUser *user) { g_return_if_fail (um != NULL); if (um->user) { g_object_unref (um->user); um->user = NULL; } um->user = g_object_ref (user); }