gnome-control-center/panels/user-accounts/cc-avatar-chooser.c
velsinki 38ed5ec2df users: close CcAvatarChooser before GtkFileDialog
This makes sure the focus is immediately on the GtkFileDialog instead of
the CcAvatarChooser behind it. This was already added in !1685, but was
accidentally removed in !1822.

Fixes #2315
2023-07-11 11:30:46 +00:00

450 lines
15 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 Red Hat, Inc,
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <adwaita.h>
#include <gio/gunixoutputstream.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <act/act.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-desktop-thumbnail.h>
#include "cc-avatar-chooser.h"
#include "cc-crop-area.h"
#include "user-utils.h"
#define ROW_SPAN 5
#define AVATAR_CHOOSER_PIXEL_SIZE 80
#define PIXEL_SIZE 512
struct _CcAvatarChooser {
GtkPopover parent;
GtkWidget *transient_for;
GtkWidget *crop_area;
GtkWidget *user_flowbox;
GtkWidget *flowbox;
GnomeDesktopThumbnailFactory *thumb_factory;
GListStore *faces;
ActUser *user;
};
G_DEFINE_TYPE (CcAvatarChooser, cc_avatar_chooser, GTK_TYPE_POPOVER)
static void
crop_dialog_response (CcAvatarChooser *self,
gint response_id,
GtkWidget *dialog)
{
g_autoptr(GdkPixbuf) pb = NULL;
g_autoptr(GdkPixbuf) pb2 = NULL;
if (response_id != GTK_RESPONSE_ACCEPT) {
self->crop_area = NULL;
gtk_window_destroy (GTK_WINDOW (dialog));
return;
}
pb = cc_crop_area_create_pixbuf (CC_CROP_AREA (self->crop_area));
pb2 = gdk_pixbuf_scale_simple (pb, PIXEL_SIZE, PIXEL_SIZE, GDK_INTERP_BILINEAR);
set_user_icon_data (self->user, pb2);
self->crop_area = NULL;
gtk_window_destroy (GTK_WINDOW (dialog));
gtk_popover_popdown (GTK_POPOVER (self));
}
static void
cc_avatar_chooser_crop (CcAvatarChooser *self,
GdkPixbuf *pixbuf)
{
GtkWidget *dialog;
GtkWindow *toplevel;
toplevel = (GtkWindow *)gtk_widget_get_native (GTK_WIDGET (self->transient_for));
dialog = gtk_dialog_new_with_buttons ("",
toplevel,
GTK_DIALOG_USE_HEADER_BAR,
_("_Cancel"),
GTK_RESPONSE_CANCEL,
_("Select"),
GTK_RESPONSE_ACCEPT,
NULL);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
g_signal_connect_object (G_OBJECT (dialog), "response",
G_CALLBACK (crop_dialog_response), self, G_CONNECT_SWAPPED);
/* Content */
self->crop_area = cc_crop_area_new ();
cc_crop_area_set_min_size (CC_CROP_AREA (self->crop_area), 48, 48);
cc_crop_area_set_paintable (CC_CROP_AREA (self->crop_area),
GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf)));
gtk_box_prepend (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
self->crop_area);
gtk_widget_set_hexpand (self->crop_area, TRUE);
gtk_widget_set_vexpand (self->crop_area, TRUE);
gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
gtk_window_present (GTK_WINDOW (dialog));
}
static void
file_dialog_open_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
CcAvatarChooser *self = CC_AVATAR_CHOOSER (user_data);
GtkFileDialog *file_dialog = GTK_FILE_DIALOG (source_object);
g_autoptr(GError) error = NULL;
g_autoptr(GdkPixbuf) pixbuf = NULL;
g_autoptr(GdkPixbuf) pixbuf2 = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GFileInputStream) stream = NULL;
file = gtk_file_dialog_open_finish (file_dialog, res, &error);
if (error != NULL) {
g_warning ("Failed to pick avatar image: %s", error->message);
return;
}
stream = g_file_read (file, NULL, &error);
pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (stream),
NULL, &error);
if (pixbuf == NULL) {
g_warning ("Failed to load %s: %s", g_file_get_uri (file), error->message);
}
pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf);
cc_avatar_chooser_crop (self, pixbuf2);
}
static void
cc_avatar_chooser_select_file (CcAvatarChooser *self)
{
g_autoptr(GFile) pictures_folder = NULL;
GtkFileDialog *file_dialog;
GtkFileFilter *filter;
GListStore *filters;
GtkWindow *toplevel;
g_return_if_fail (CC_IS_AVATAR_CHOOSER (self));
toplevel = GTK_WINDOW (gtk_widget_get_native (GTK_WIDGET (self->transient_for)));
file_dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_title (file_dialog, _("Browse for more pictures"));
gtk_file_dialog_set_modal (file_dialog, TRUE);
filter = gtk_file_filter_new ();
gtk_file_filter_add_pixbuf_formats (filter);
filters = g_list_store_new (GTK_TYPE_FILE_FILTER);
g_list_store_append (filters, filter);
gtk_file_dialog_set_filters (file_dialog, G_LIST_MODEL (filters));
pictures_folder = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
gtk_file_dialog_set_initial_folder (file_dialog, pictures_folder);
gtk_popover_popdown (GTK_POPOVER (self));
gtk_file_dialog_open (file_dialog,
toplevel,
NULL,
file_dialog_open_cb,
self);
}
static void
face_widget_activated (CcAvatarChooser *self,
GtkFlowBoxChild *child)
{
const gchar *filename;
GtkWidget *image;
image = gtk_flow_box_child_get_child (child);
filename = g_object_get_data (G_OBJECT (image), "filename");
act_user_set_icon_file (self->user, filename);
gtk_popover_popdown (GTK_POPOVER (self));
}
static GtkWidget *
create_face_widget (gpointer item,
gpointer user_data)
{
g_autofree gchar *image_path = NULL;
g_autoptr(GdkPixbuf) source_pixbuf = NULL;
g_autoptr(GdkPixbuf) pixbuf = NULL;
GtkWidget *image;
image_path = g_file_get_path (G_FILE (item));
source_pixbuf = gdk_pixbuf_new_from_file_at_size (image_path,
AVATAR_CHOOSER_PIXEL_SIZE,
AVATAR_CHOOSER_PIXEL_SIZE,
NULL);
if (source_pixbuf == NULL) {
image = gtk_image_new_from_icon_name ("image-missing");
gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE);
g_object_set_data_full (G_OBJECT (image),
"filename", g_steal_pointer (&image_path), g_free);
return image;
}
pixbuf = round_image (source_pixbuf);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_image_set_pixel_size (GTK_IMAGE (image), AVATAR_CHOOSER_PIXEL_SIZE);
g_object_set_data_full (G_OBJECT (image),
"filename", g_steal_pointer (&image_path), g_free);
return image;
}
static GStrv
get_settings_facesdirs (void)
{
g_autoptr(GSettings) settings = g_settings_new ("org.gnome.desktop.interface");
g_auto(GStrv) settings_dirs = g_settings_get_strv (settings, "avatar-directories");
g_autoptr(GPtrArray) facesdirs = g_ptr_array_new ();
if (settings_dirs != NULL) {
int i;
for (i = 0; settings_dirs[i] != NULL; i++) {
char *path = settings_dirs[i];
if (g_strcmp0 (path, "") != 0)
g_ptr_array_add (facesdirs, g_strdup (path));
}
}
g_ptr_array_add (facesdirs, NULL);
return (GStrv) g_ptr_array_steal (facesdirs, NULL);
}
static GStrv
get_system_facesdirs (void)
{
const char * const * data_dirs;
g_autoptr(GPtrArray) facesdirs = NULL;
int i;
facesdirs = g_ptr_array_new ();
data_dirs = g_get_system_data_dirs ();
for (i = 0; data_dirs[i] != NULL; i++) {
char *path = g_build_filename (data_dirs[i], "pixmaps", "faces", NULL);
g_ptr_array_add (facesdirs, path);
}
g_ptr_array_add (facesdirs, NULL);
return (GStrv) g_ptr_array_steal (facesdirs, NULL);
}
static gboolean
add_faces_from_dirs (GListStore *faces, GStrv facesdirs, gboolean add_all)
{
gboolean added_faces = FALSE;
for (guint i = 0; facesdirs[i] != NULL; i++) {
g_autoptr(GFile) dir = NULL;
g_autoptr(GFileEnumerator) enumerator = NULL;
dir = g_file_new_for_path (facesdirs[i]);
enumerator = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
G_FILE_QUERY_INFO_NONE,
NULL, NULL);
if (enumerator == NULL) {
continue;
}
while (TRUE) {
GFile *file;
GFileType type;
const gchar *target;
g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, NULL, NULL);
if (info == NULL) {
break;
}
type = g_file_info_get_file_type (info);
if (type != G_FILE_TYPE_REGULAR &&
type != G_FILE_TYPE_SYMBOLIC_LINK) {
continue;
}
target = g_file_info_get_attribute_byte_string (info,
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
if (target != NULL && g_str_has_prefix (target , "legacy/")) {
continue;
}
file = g_file_get_child (dir, g_file_info_get_name (info));
g_list_store_append (faces, file);
added_faces = TRUE;
}
g_file_enumerator_close (enumerator, NULL, NULL);
if (added_faces && !add_all)
break;
}
return added_faces;
}
static void
setup_photo_popup (CcAvatarChooser *self)
{
g_auto(GStrv) settings_facesdirs = NULL;
self->faces = g_list_store_new (G_TYPE_FILE);
gtk_flow_box_bind_model (GTK_FLOW_BOX (self->flowbox),
G_LIST_MODEL (self->faces),
create_face_widget,
self,
NULL);
g_signal_connect_object (self->flowbox, "child-activated",
G_CALLBACK (face_widget_activated), self, G_CONNECT_SWAPPED);
settings_facesdirs = get_settings_facesdirs ();
if (!add_faces_from_dirs (self->faces, settings_facesdirs, TRUE)) {
g_auto(GStrv) system_facesdirs = get_system_facesdirs ();
add_faces_from_dirs (self->faces, system_facesdirs, FALSE);
}
}
CcAvatarChooser *
cc_avatar_chooser_new (GtkWidget *transient_for)
{
CcAvatarChooser *self;
self = g_object_new (CC_TYPE_AVATAR_CHOOSER,
NULL);
self->transient_for = transient_for;
self->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
setup_photo_popup (self);
return self;
}
static void
cc_avatar_chooser_dispose (GObject *object)
{
CcAvatarChooser *self = CC_AVATAR_CHOOSER (object);
g_clear_object (&self->thumb_factory);
g_clear_object (&self->user);
G_OBJECT_CLASS (cc_avatar_chooser_parent_class)->dispose (object);
}
static void
cc_avatar_chooser_init (CcAvatarChooser *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
static void
cc_avatar_chooser_class_init (CcAvatarChooserClass *klass)
{
GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
GObjectClass *oclass = G_OBJECT_CLASS (klass);
gtk_widget_class_set_template_from_resource (wclass, "/org/gnome/control-center/user-accounts/cc-avatar-chooser.ui");
gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, user_flowbox);
gtk_widget_class_bind_template_child (wclass, CcAvatarChooser, flowbox);
gtk_widget_class_bind_template_callback (wclass, cc_avatar_chooser_select_file);
oclass->dispose = cc_avatar_chooser_dispose;
}
static void
user_flowbox_activated (CcAvatarChooser *self)
{
set_default_avatar (self->user);
gtk_popover_popdown (GTK_POPOVER (self));
}
void
cc_avatar_chooser_set_user (CcAvatarChooser *self,
ActUser *user)
{
g_autoptr(GdkPixbuf) pixbuf = NULL;
const gchar *name;
GtkWidget *avatar;
g_return_if_fail (self != NULL);
if (self->user) {
GtkWidget *child;
child = gtk_widget_get_first_child (GTK_WIDGET (self->user_flowbox));
while (child) {
GtkWidget *next = gtk_widget_get_next_sibling (child);
if (GTK_FLOW_BOX_CHILD (child))
gtk_flow_box_remove (GTK_FLOW_BOX (self->user_flowbox), child);
child = next;
}
g_clear_object (&self->user);
}
self->user = g_object_ref (user);
name = act_user_get_real_name (user);
if (name == NULL)
name = act_user_get_user_name (user);
avatar = adw_avatar_new (AVATAR_CHOOSER_PIXEL_SIZE, name, TRUE);
gtk_flow_box_append (GTK_FLOW_BOX (self->user_flowbox), avatar);
g_signal_connect_object (self->user_flowbox, "child-activated", G_CALLBACK (user_flowbox_activated), self, G_CONNECT_SWAPPED);
}