]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-avatar-chooser.c
re-order functions so we can get rid of their declarations
[empathy.git] / libempathy-gtk / empathy-avatar-chooser.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2006-2007 Imendio AB.
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Based on Novell's e-image-chooser.
21  *          Xavier Claessens <xclaesse@gmail.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27
28 #include <glib/gi18n-lib.h>
29 #include <gtk/gtk.h>
30 #include <gio/gio.h>
31
32 #include <libempathy/empathy-gsettings.h>
33 #include <libempathy/empathy-utils.h>
34
35 #include "empathy-avatar-chooser.h"
36 #include "empathy-images.h"
37 #include "empathy-ui-utils.h"
38
39 #ifdef HAVE_CHEESE
40 #include <cheese-avatar-chooser.h>
41 #endif /* HAVE_CHEESE */
42
43
44 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
45 #include <libempathy/empathy-debug.h>
46
47 /**
48  * SECTION:empathy-avatar-chooser
49  * @title: EmpathyAvatarChooser
50  * @short_description: A widget used to change avatar
51  * @include: libempathy-gtk/empathy-avatar-chooser.h
52  *
53  * #EmpathyAvatarChooser is a widget which extends #GtkButton to
54  * provide a way of changing avatar.
55  */
56
57 /**
58  * EmpathyAvatarChooser:
59  * @parent: parent object
60  *
61  * Widget which extends #GtkButton to provide a way of changing avatar.
62  */
63
64 #define AVATAR_SIZE_SAVE 96
65 #define AVATAR_SIZE_VIEW 64
66 #define DEFAULT_DIR DATADIR"/pixmaps/faces"
67
68 #ifdef HAVE_CHEESE
69 /*
70  * A custom GtkResponseType used when the user presses the
71  * "Camera Picture" button. Any positive value would be sufficient.
72  */
73 #define EMPATHY_AVATAR_CHOOSER_RESPONSE_WEBCAM   10
74 #endif
75 #define EMPATHY_AVATAR_CHOOSER_RESPONSE_NO_IMAGE GTK_RESPONSE_NO
76 #define EMPATHY_AVATAR_CHOOSER_RESPONSE_CANCEL   GTK_RESPONSE_CANCEL
77 #define EMPATHY_AVATAR_CHOOSER_RESPONSE_FILE     GTK_RESPONSE_OK
78
79 struct _EmpathyAvatarChooserPrivate {
80         TpConnection            *connection;
81         GtkFileChooser          *chooser_dialog;
82
83         gulong ready_handler_id;
84
85         EmpathyAvatar *avatar;
86         GSettings *gsettings_ui;
87 };
88
89 enum {
90         CHANGED,
91         LAST_SIGNAL
92 };
93
94 enum {
95         PROP_0,
96         PROP_CONNECTION
97 };
98
99 static guint signals [LAST_SIGNAL];
100
101 G_DEFINE_TYPE (EmpathyAvatarChooser, empathy_avatar_chooser, GTK_TYPE_BUTTON);
102
103 /*
104  * Drag and drop stuff
105  */
106 #define URI_LIST_TYPE "text/uri-list"
107
108 enum DndTargetType {
109         DND_TARGET_TYPE_URI_LIST
110 };
111
112 static const GtkTargetEntry drop_types[] = {
113         { URI_LIST_TYPE, 0, DND_TARGET_TYPE_URI_LIST },
114 };
115
116 static void
117 avatar_chooser_get_property (GObject    *object,
118                              guint       param_id,
119                              GValue     *value,
120                              GParamSpec *pspec)
121 {
122         EmpathyAvatarChooser *self = (EmpathyAvatarChooser *) object;
123
124         switch (param_id) {
125         case PROP_CONNECTION:
126                 g_value_set_object (value, self->priv->connection);
127                 break;
128         default:
129                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
130                 break;
131         }
132 }
133
134 static void
135 avatar_chooser_set_connection (EmpathyAvatarChooser *self,
136                                TpConnection         *connection)
137 {
138         tp_clear_object (&self->priv->connection);
139
140         if (connection != NULL) {
141                 GQuark features[] = { TP_CONNECTION_FEATURE_AVATAR_REQUIREMENTS, 0 };
142                 self->priv->connection = g_object_ref (connection);
143                 tp_proxy_prepare_async (self->priv->connection, features, NULL, NULL);
144         }
145 }
146
147 static void
148 avatar_chooser_set_property (GObject      *object,
149                              guint         param_id,
150                              const GValue *value,
151                              GParamSpec   *pspec)
152 {
153         EmpathyAvatarChooser *self = EMPATHY_AVATAR_CHOOSER (object);
154
155         switch (param_id) {
156         case PROP_CONNECTION:
157                 avatar_chooser_set_connection (self, g_value_get_object (value));
158                 break;
159         default:
160                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
161                 break;
162         }
163 }
164
165 static void
166 avatar_chooser_finalize (GObject *object)
167 {
168         EmpathyAvatarChooser *self = (EmpathyAvatarChooser *) object;
169
170         avatar_chooser_set_connection (EMPATHY_AVATAR_CHOOSER (object), NULL);
171         g_assert (self->priv->connection == NULL);
172
173         tp_clear_pointer (&self->priv->avatar, empathy_avatar_unref);
174
175         tp_clear_object (&self->priv->gsettings_ui);
176
177         G_OBJECT_CLASS (empathy_avatar_chooser_parent_class)->finalize (object);
178 }
179
180 static void
181 empathy_avatar_chooser_class_init (EmpathyAvatarChooserClass *klass)
182 {
183         GObjectClass *object_class = G_OBJECT_CLASS (klass);
184         GParamSpec *param_spec;
185
186         object_class->finalize = avatar_chooser_finalize;
187         object_class->get_property = avatar_chooser_get_property;
188         object_class->set_property = avatar_chooser_set_property;
189
190         /**
191          * EmpathyAvatarChooser::changed:
192          * @self: an #EmpathyAvatarChooser
193          *
194          * Emitted when the chosen avatar has changed.
195          *
196          */
197         signals[CHANGED] =
198                 g_signal_new ("changed",
199                               G_TYPE_FROM_CLASS (klass),
200                               G_SIGNAL_RUN_LAST,
201                               0,
202                               NULL, NULL,
203                               g_cclosure_marshal_VOID__VOID,
204                               G_TYPE_NONE, 0);
205
206         /**
207          * EmpathyAvatarChooser:connection:
208          *
209          * The #TpConnection whose avatar should be shown and modified by
210          * the #EmpathyAvatarChooser instance.
211          */
212         param_spec = g_param_spec_object ("connection",
213                                           "TpConnection",
214                                           "TpConnection whose avatar should be "
215                                           "shown and modified by this widget",
216                                           TP_TYPE_CONNECTION,
217                                           G_PARAM_READWRITE |
218                                           G_PARAM_STATIC_STRINGS);
219         g_object_class_install_property (object_class,
220                                          PROP_CONNECTION,
221                                          param_spec);
222
223         g_type_class_add_private (object_class, sizeof (EmpathyAvatarChooserPrivate));
224 }
225
226 static gboolean
227 avatar_chooser_drag_motion_cb (GtkWidget          *widget,
228                               GdkDragContext     *context,
229                               gint                x,
230                               gint                y,
231                               guint               time_,
232                               EmpathyAvatarChooser *self)
233 {
234         GList                  *p;
235
236         for (p = gdk_drag_context_list_targets (context); p != NULL;
237              p = p->next) {
238                 gchar *possible_type;
239
240                 possible_type = gdk_atom_name (GDK_POINTER_TO_ATOM (p->data));
241
242                 if (!strcmp (possible_type, URI_LIST_TYPE)) {
243                         g_free (possible_type);
244                         gdk_drag_status (context, GDK_ACTION_COPY, time_);
245
246                         return TRUE;
247                 }
248
249                 g_free (possible_type);
250         }
251
252         return FALSE;
253 }
254
255 static void
256 avatar_chooser_drag_leave_cb (GtkWidget          *widget,
257                              GdkDragContext     *context,
258                              guint               time_,
259                              EmpathyAvatarChooser *self)
260 {
261 }
262
263 static gboolean
264 avatar_chooser_drag_drop_cb (GtkWidget          *widget,
265                             GdkDragContext     *context,
266                             gint                x,
267                             gint                y,
268                             guint               time_,
269                             EmpathyAvatarChooser *self)
270 {
271         GList                  *p;
272
273         if (gdk_drag_context_list_targets (context) == NULL) {
274                 return FALSE;
275         }
276
277         for (p = gdk_drag_context_list_targets (context);
278              p != NULL; p = p->next) {
279                 char *possible_type;
280
281                 possible_type = gdk_atom_name (GDK_POINTER_TO_ATOM (p->data));
282                 if (!strcmp (possible_type, URI_LIST_TYPE)) {
283                         g_free (possible_type);
284                         gtk_drag_get_data (widget, context,
285                                            GDK_POINTER_TO_ATOM (p->data),
286                                            time_);
287
288                         return TRUE;
289                 }
290
291                 g_free (possible_type);
292         }
293
294         return FALSE;
295 }
296
297 static void
298 avatar_chooser_clear_image (EmpathyAvatarChooser *self)
299 {
300         GtkWidget *image;
301
302         tp_clear_pointer (&self->priv->avatar, empathy_avatar_unref);
303
304         image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
305                 GTK_ICON_SIZE_DIALOG);
306         gtk_button_set_image (GTK_BUTTON (self), image);
307         g_signal_emit (self, signals[CHANGED], 0);
308 }
309
310 static gboolean
311 str_in_strv (const gchar  *str,
312              gchar **strv)
313 {
314         if (strv == NULL) {
315                 return FALSE;
316         }
317
318         while (*strv != NULL) {
319                 if (g_str_equal (str, *strv)) {
320                         return TRUE;
321                 }
322                 strv++;
323         }
324         return FALSE;
325 }
326
327 /* The caller must free the strings stored in satisfactory_format_name and
328  * satisfactory_mime_type.
329  */
330 static gboolean
331 avatar_chooser_need_mime_type_conversion (const gchar *current_mime_type,
332                                           gchar      **accepted_mime_types,
333                                           gchar      **satisfactory_format_name,
334                                           gchar      **satisfactory_mime_type)
335 {
336         gchar   *good_mime_types[] = {"image/jpeg", "image/png", NULL};
337         guint    i;
338         GSList  *formats, *l;
339         gboolean found = FALSE;
340
341         *satisfactory_format_name = NULL;
342         *satisfactory_mime_type = NULL;
343
344         /* If there is no accepted format there is nothing we can do */
345         if (accepted_mime_types == NULL || *accepted_mime_types == NULL) {
346                 return TRUE;
347         }
348
349         /* If the current mime type is good and accepted, don't change it!
350          * jpeg is compress better pictures, but png is better for logos and
351          * could have an alpha layer. */
352         if (str_in_strv (current_mime_type, good_mime_types) &&
353             str_in_strv (current_mime_type, accepted_mime_types)) {
354                 *satisfactory_mime_type = g_strdup (current_mime_type);
355                 *satisfactory_format_name = g_strdup (current_mime_type +
356                                                       strlen ("image/"));
357                 return FALSE;
358         }
359
360         /* The current mime type is either not accepted or not good to use.
361          * Check if one of the good format is supported... */
362         for (i = 0; good_mime_types[i] != NULL;  i++) {
363                 if (str_in_strv (good_mime_types[i], accepted_mime_types)) {
364                         *satisfactory_mime_type = g_strdup (good_mime_types[i]);
365                         *satisfactory_format_name = g_strdup (good_mime_types[i] +
366                                                               strlen ("image/"));
367                         return TRUE;
368                 }
369         }
370
371         /* Pick the first supported format we can write */
372         formats = gdk_pixbuf_get_formats ();
373         for (l = formats; !found && l != NULL; l = l->next) {
374                 GdkPixbufFormat *format = l->data;
375                 gchar **format_mime_types;
376                 gchar **iter;
377
378                 if (!gdk_pixbuf_format_is_writable (format)) {
379                         continue;
380                 }
381
382                 format_mime_types = gdk_pixbuf_format_get_mime_types (format);
383                 for (iter = format_mime_types; *iter != NULL; iter++) {
384                         if (str_in_strv (*iter, accepted_mime_types)) {
385                                 *satisfactory_format_name = gdk_pixbuf_format_get_name (format);
386                                 *satisfactory_mime_type = g_strdup (*iter);
387                                 found = TRUE;
388                                 break;
389                         }
390                 }
391                 g_strfreev (format_mime_types);
392         }
393         g_slist_free (formats);
394
395         return TRUE;
396 }
397
398 static void
399 avatar_chooser_error_show (EmpathyAvatarChooser *self,
400                            const gchar          *primary_text,
401                            const gchar          *secondary_text)
402 {
403         GtkWidget *parent;
404         GtkWidget *dialog;
405
406         parent = gtk_widget_get_toplevel (GTK_WIDGET (self));
407         if (!GTK_IS_WINDOW (parent)) {
408                 parent = NULL;
409         }
410
411         dialog = gtk_message_dialog_new (parent ? GTK_WINDOW (parent) : NULL,
412                                          GTK_DIALOG_MODAL,
413                                          GTK_MESSAGE_WARNING,
414                                          GTK_BUTTONS_CLOSE,
415                                          "%s", primary_text);
416
417         if (secondary_text != NULL) {
418                 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
419                                                           "%s", secondary_text);
420         }
421
422         g_signal_connect (dialog, "response",
423                           G_CALLBACK (gtk_widget_destroy), NULL);
424         gtk_widget_show (dialog);
425
426 }
427
428 static EmpathyAvatar *
429 avatar_chooser_maybe_convert_and_scale (EmpathyAvatarChooser *self,
430                                         GdkPixbuf            *pixbuf,
431                                         EmpathyAvatar        *avatar)
432 {
433         TpAvatarRequirements     *req;
434         gboolean                  needs_conversion = FALSE;
435         guint                     width, height;
436         gchar                    *new_format_name = NULL;
437         gchar                    *new_mime_type = NULL;
438         gdouble                   min_factor, max_factor;
439         gdouble                   factor;
440         gchar                    *best_image_data = NULL;
441         gsize                     best_image_size = 0;
442         guint                     count = 0;
443
444         req = tp_connection_get_avatar_requirements (self->priv->connection);
445         if (req == NULL) {
446                 DEBUG ("Avatar requirements not ready");
447                 return NULL;
448         }
449
450         /* Smaller is the factor, smaller will be the image.
451          * 0 is an empty image, 1 is the full size. */
452         min_factor = 0;
453         max_factor = 1;
454         factor = 1;
455
456         /* Check if we need to convert to another image format */
457         if (avatar_chooser_need_mime_type_conversion (avatar->format,
458                                                       req->supported_mime_types,
459                                                       &new_format_name,
460                                                       &new_mime_type)) {
461                 DEBUG ("Format conversion needed, we'll use mime type '%s' "
462                        "and format name '%s'. Current mime type is '%s'",
463                        new_mime_type, new_format_name, avatar->format);
464                 needs_conversion = TRUE;
465         }
466
467         /* If there is no format we can use, report error to the user. */
468         if (new_mime_type == NULL || new_format_name == NULL) {
469                 avatar_chooser_error_show (self, _("Couldn't convert image"),
470                                 _("None of the accepted image formats are "
471                                   "supported on your system"));
472                 return NULL;
473         }
474
475         /* If width or height are too big, it needs converting. */
476         width = gdk_pixbuf_get_width (pixbuf);
477         height = gdk_pixbuf_get_height (pixbuf);
478         if ((req->maximum_width > 0 && width > req->maximum_width) ||
479             (req->maximum_height > 0 && height > req->maximum_height)) {
480                 gdouble h_factor, v_factor;
481
482                 h_factor = (gdouble) req->maximum_width / width;
483                 v_factor = (gdouble) req->maximum_height / height;
484                 factor = max_factor = MIN (h_factor, v_factor);
485
486                 DEBUG ("Image dimensions (%dx%d) are too big. Max is %dx%d.",
487                        width, height, req->maximum_width, req->maximum_height);
488
489                 needs_conversion = TRUE;
490         }
491
492         /* If the data len is too big and no other conversion is needed,
493          * try with a lower factor. */
494         if (req->maximum_bytes > 0 && avatar->len > req->maximum_bytes && !needs_conversion) {
495                 DEBUG ("Image data (%"G_GSIZE_FORMAT" bytes) is too big "
496                        "(max is %u bytes), conversion needed.",
497                        avatar->len, req->maximum_bytes);
498
499                 factor = 0.5;
500                 needs_conversion = TRUE;
501         }
502
503         /* If no conversion is needed, return the avatar */
504         if (!needs_conversion) {
505                 g_free (new_format_name);
506                 g_free (new_mime_type);
507                 return empathy_avatar_ref (avatar);
508         }
509
510         do {
511                 GdkPixbuf *pixbuf_scaled = NULL;
512                 gboolean   saved;
513                 gint       new_width, new_height;
514                 gchar     *converted_image_data;
515                 gsize      converted_image_size;
516                 GError    *error = NULL;
517
518                 if (factor != 1) {
519                         new_width = width * factor;
520                         new_height = height * factor;
521                         pixbuf_scaled = gdk_pixbuf_scale_simple (pixbuf,
522                                                                  new_width,
523                                                                  new_height,
524                                                                  GDK_INTERP_HYPER);
525                 } else {
526                         new_width = width;
527                         new_height = height;
528                         pixbuf_scaled = g_object_ref (pixbuf);
529                 }
530
531                 DEBUG ("Trying with factor %f (%dx%d) and format %s...", factor,
532                         new_width, new_height, new_format_name);
533
534                 saved = gdk_pixbuf_save_to_buffer (pixbuf_scaled,
535                                                    &converted_image_data,
536                                                    &converted_image_size,
537                                                    new_format_name,
538                                                    &error, NULL);
539                 g_object_unref (pixbuf_scaled);
540
541                 if (!saved) {
542                         g_free (new_format_name);
543                         g_free (new_mime_type);
544                         avatar_chooser_error_show (self,
545                                 _("Couldn't convert image"),
546                                 error ? error->message : NULL);
547                         g_clear_error (&error);
548                         return NULL;
549                 }
550
551                 DEBUG ("Produced an image data of %"G_GSIZE_FORMAT" bytes.",
552                         converted_image_size);
553
554                 /* If the new image satisfy the req, keep it as current best */
555                 if (req->maximum_bytes == 0 ||
556                     converted_image_size <= req->maximum_bytes) {
557                         if (best_image_data)
558                                 g_free (best_image_data);
559
560                         best_image_data = converted_image_data;
561                         best_image_size = converted_image_size;
562
563                         /* If this image is close enough to the optimal size,
564                          * stop searching */
565                         if (req->maximum_bytes == 0 ||
566                             req->maximum_bytes - converted_image_size <= 1024)
567                                 break;
568                 } else {
569                         g_free (converted_image_data);
570                 }
571
572                 /* Make a binary search for the bigest factor that produce
573                  * an image data size less than max_size */
574                 if (converted_image_size > req->maximum_bytes)
575                         max_factor = factor;
576                 if (converted_image_size < req->maximum_bytes)
577                         min_factor = factor;
578                 factor = (min_factor + max_factor)/2;
579
580                 if ((int) (width * factor) == new_width ||
581                     (int) (height * factor) == new_height) {
582                         /* min_factor and max_factor are too close, so the new
583                          * factor will produce the same image as previous
584                          * iteration. No need to continue, we already found
585                          * the optimal size. */
586                         break;
587                 }
588
589                 /* Do 10 iterations in the worst case */
590         } while (++count < 10);
591
592         g_free (new_format_name);
593
594         /* Takes ownership of new_mime_type and best_image_data */
595         avatar = empathy_avatar_new ((guchar *) best_image_data,
596                 best_image_size, new_mime_type, NULL);
597
598         return avatar;
599 }
600
601 static void
602 avatar_chooser_set_image (EmpathyAvatarChooser *self,
603                           EmpathyAvatar        *avatar,
604                           GdkPixbuf            *pixbuf,
605                           gboolean              set_locally)
606 {
607         GdkPixbuf                *pixbuf_view;
608         GtkWidget                *image;
609
610         g_assert (avatar != NULL);
611         g_assert (pixbuf != NULL);
612
613         if (set_locally) {
614                 EmpathyAvatar *conv;
615
616                 conv = avatar_chooser_maybe_convert_and_scale (self,
617                         pixbuf, avatar);
618                 empathy_avatar_unref (avatar);
619
620                 if (conv == NULL) {
621                         /* An error occured; don't change the avatar. */
622                         return;
623                 }
624
625                 avatar = conv;
626         }
627
628         tp_clear_pointer (&self->priv->avatar, empathy_avatar_unref);
629         self->priv->avatar = avatar;
630
631         pixbuf_view = empathy_pixbuf_scale_down_if_necessary (pixbuf, AVATAR_SIZE_VIEW);
632         image = gtk_image_new_from_pixbuf (pixbuf_view);
633
634         gtk_button_set_image (GTK_BUTTON (self), image);
635         g_signal_emit (self, signals[CHANGED], 0);
636
637         g_object_unref (pixbuf_view);
638         g_object_unref (pixbuf);
639 }
640
641 static void
642 avatar_chooser_set_image_from_data (EmpathyAvatarChooser *self,
643                                     gchar                *data,
644                                     gsize                 size,
645                                     gboolean              set_locally)
646 {
647         GdkPixbuf     *pixbuf;
648         EmpathyAvatar *avatar = NULL;
649         gchar         *mime_type = NULL;
650
651         if (data == NULL) {
652                 avatar_chooser_clear_image (self);
653                 return;
654         }
655
656         pixbuf = empathy_pixbuf_from_data_and_mime (data, size, &mime_type);
657         if (pixbuf == NULL) {
658                 g_free (data);
659                 data = NULL;
660                 return;
661         }
662
663         /* avatar takes ownership of data and mime_type */
664         avatar = empathy_avatar_new ((guchar *) data, size, mime_type, NULL);
665
666         avatar_chooser_set_image (self, avatar, pixbuf, set_locally);
667 }
668
669 static void
670 avatar_chooser_drag_data_received_cb (GtkWidget          *widget,
671                                      GdkDragContext     *context,
672                                      gint                x,
673                                      gint                y,
674                                      GtkSelectionData   *selection_data,
675                                      guint               info,
676                                      guint               time_,
677                                      EmpathyAvatarChooser *self)
678 {
679         gchar    *target_type;
680         gboolean  handled = FALSE;
681
682         target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
683         if (!strcmp (target_type, URI_LIST_TYPE)) {
684                 GFile            *file;
685                 gchar            *nl;
686                 gchar            *data = NULL;
687                 gsize             bytes_read;
688
689                 nl = strstr ((gchar *) gtk_selection_data_get_data (selection_data),
690                                                 "\r\n");
691                 if (nl) {
692                         gchar *uri;
693
694                         uri = g_strndup ((gchar *) gtk_selection_data_get_data (selection_data),
695                                          nl - (gchar *) gtk_selection_data_get_data (selection_data));
696
697                         file = g_file_new_for_uri (uri);
698                         g_free (uri);
699                 } else {
700                         file = g_file_new_for_uri ((gchar *) gtk_selection_data_get_data (
701                                                 selection_data));
702                 }
703
704                 handled = g_file_load_contents (file, NULL, &data, &bytes_read,
705                                                 NULL, NULL);
706
707                 if (handled) {
708                         /* this in turn calls empathy_avatar_new (), which assumes
709                          * ownership of data.
710                          */
711                         avatar_chooser_set_image_from_data (self, data,
712                                                             bytes_read,
713                                                             TRUE);
714                 }
715
716                 g_object_unref (file);
717         }
718
719         gtk_drag_finish (context, handled, FALSE, time_);
720 }
721
722 static void
723 avatar_chooser_update_preview_cb (GtkFileChooser       *file_chooser,
724                                   EmpathyAvatarChooser *self)
725 {
726         gchar *filename;
727
728         filename = gtk_file_chooser_get_preview_filename (file_chooser);
729
730         if (filename) {
731                 GtkWidget *image;
732                 GdkPixbuf *pixbuf = NULL;
733                 GdkPixbuf *scaled_pixbuf;
734
735                 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
736
737                 image = gtk_file_chooser_get_preview_widget (file_chooser);
738
739                 if (pixbuf) {
740                         scaled_pixbuf = empathy_pixbuf_scale_down_if_necessary (pixbuf, AVATAR_SIZE_SAVE);
741                         gtk_image_set_from_pixbuf (GTK_IMAGE (image), scaled_pixbuf);
742                         g_object_unref (scaled_pixbuf);
743                         g_object_unref (pixbuf);
744                 } else {
745                         gtk_image_set_from_stock (GTK_IMAGE (image),
746                                                   "gtk-dialog-question",
747                                                   GTK_ICON_SIZE_DIALOG);
748                 }
749
750                 g_free (filename);
751         }
752
753         gtk_file_chooser_set_preview_widget_active (file_chooser, TRUE);
754 }
755
756 static void
757 avatar_chooser_set_image_from_file (EmpathyAvatarChooser *self,
758                                     const gchar          *filename)
759 {
760         gchar  *image_data = NULL;
761         gsize   image_size = 0;
762         GError *error = NULL;
763
764         if (!g_file_get_contents (filename, &image_data, &image_size, &error)) {
765                 DEBUG ("Failed to load image from '%s': %s", filename,
766                         error ? error->message : "No error given");
767
768                 g_clear_error (&error);
769                 return;
770         }
771
772         avatar_chooser_set_image_from_data (self, image_data, image_size, TRUE);
773 }
774
775 #ifdef HAVE_CHEESE
776 static void
777 avatar_chooser_set_avatar_from_pixbuf (EmpathyAvatarChooser *self,
778                                        GdkPixbuf            *pb)
779 {
780         /* dup the string as empathy_avatar_new steals ownership of the it */
781         gchar         *mime = g_strdup ("image/png");
782         gsize          size;
783         gchar         *buf;
784         EmpathyAvatar *avatar = NULL;
785         GError        *error = NULL;
786         if (!gdk_pixbuf_save_to_buffer (pb, &buf, &size, "png", &error, NULL)) {
787                 avatar_chooser_error_show (self,
788                         _("Couldn't save pixbuf to png"),
789                         error ? error->message : NULL);
790                 g_clear_error (&error);
791                 return;
792         }
793         avatar = empathy_avatar_new ((guchar *) buf, size, mime, NULL);
794         avatar_chooser_set_image (self, avatar, pb, TRUE);
795 }
796
797 static gboolean
798 destroy_chooser (GtkWidget *self)
799 {
800         gtk_widget_destroy (self);
801         return FALSE;
802 }
803
804 static void
805 webcam_response_cb (GtkDialog            *dialog,
806                     int                   response,
807                     EmpathyAvatarChooser *self)
808 {
809         if (response == GTK_RESPONSE_ACCEPT) {
810                 GdkPixbuf           *pb;
811                 CheeseAvatarChooser *cheese_chooser;
812
813                 cheese_chooser = CHEESE_AVATAR_CHOOSER (dialog);
814                 pb = cheese_avatar_chooser_get_picture (cheese_chooser);
815                 avatar_chooser_set_avatar_from_pixbuf (self, pb);
816         }
817         if (response != GTK_RESPONSE_DELETE_EVENT &&
818             response != GTK_RESPONSE_NONE)
819                 g_idle_add ((GSourceFunc) destroy_chooser, dialog);
820 }
821
822 static void
823 choose_avatar_from_webcam (GtkWidget            *widget,
824                            EmpathyAvatarChooser *self)
825 {
826         GtkWidget *window;
827
828         window = cheese_avatar_chooser_new ();
829
830         gtk_window_set_transient_for (GTK_WINDOW (window),
831                                       GTK_WINDOW (empathy_get_toplevel_window (GTK_WIDGET (self))));
832         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
833         g_signal_connect (G_OBJECT (window), "response",
834                           G_CALLBACK (webcam_response_cb), self);
835         gtk_widget_show (window);
836 }
837 #endif /* HAVE_CHEESE */
838
839 static void
840 avatar_chooser_response_cb (GtkWidget            *widget,
841                             gint                  response,
842                             EmpathyAvatarChooser *self)
843 {
844         self->priv->chooser_dialog = NULL;
845
846         if (response == EMPATHY_AVATAR_CHOOSER_RESPONSE_FILE) {
847                 gchar *filename;
848                 gchar *path;
849
850                 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
851                 avatar_chooser_set_image_from_file (self, filename);
852                 g_free (filename);
853
854                 path = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (widget));
855                 if (path) {
856                         g_settings_set_string (self->priv->gsettings_ui,
857                                                EMPATHY_PREFS_UI_AVATAR_DIRECTORY,
858                                                path);
859
860                         g_free (path);
861                 }
862         }
863         else if (response == EMPATHY_AVATAR_CHOOSER_RESPONSE_NO_IMAGE) {
864                 /* This corresponds to "No Image", not to "Cancel" */
865                 avatar_chooser_clear_image (self);
866         }
867         #ifdef HAVE_CHEESE
868         else if (response == EMPATHY_AVATAR_CHOOSER_RESPONSE_WEBCAM) {
869                 /* This corresponds to "Camera Picture" */
870                 choose_avatar_from_webcam (widget, self);
871         }
872         #endif
873         gtk_widget_destroy (widget);
874 }
875
876 static void
877 avatar_chooser_clicked_cb (GtkWidget            *button,
878                            EmpathyAvatarChooser *self)
879 {
880         GtkFileChooser *chooser_dialog;
881         GtkWidget      *image;
882         gchar          *saved_dir = NULL;
883         const gchar    *default_dir = DEFAULT_DIR;
884         const gchar    *pics_dir;
885         GtkFileFilter  *filter;
886
887         if (self->priv->chooser_dialog) {
888                 gtk_window_present (GTK_WINDOW (self->priv->chooser_dialog));
889                 return;
890         }
891
892         self->priv->chooser_dialog = GTK_FILE_CHOOSER (
893                 gtk_file_chooser_dialog_new (_("Select Your Avatar Image"),
894                                              empathy_get_toplevel_window (GTK_WIDGET (self)),
895                                              GTK_FILE_CHOOSER_ACTION_OPEN,
896                                              #ifdef HAVE_CHEESE
897                                              _("Take a picture..."),
898                                              EMPATHY_AVATAR_CHOOSER_RESPONSE_WEBCAM,
899                                              #endif
900                                              _("No Image"),
901                                              EMPATHY_AVATAR_CHOOSER_RESPONSE_NO_IMAGE,
902                                              GTK_STOCK_CANCEL,
903                                              EMPATHY_AVATAR_CHOOSER_RESPONSE_CANCEL,
904                                              GTK_STOCK_OPEN,
905                                              EMPATHY_AVATAR_CHOOSER_RESPONSE_FILE,
906                                              NULL));
907         chooser_dialog = self->priv->chooser_dialog;
908         gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser_dialog), TRUE);
909
910         /* Get special dirs */
911         saved_dir = g_settings_get_string (self->priv->gsettings_ui,
912                                            EMPATHY_PREFS_UI_AVATAR_DIRECTORY);
913
914         if (saved_dir && !g_file_test (saved_dir, G_FILE_TEST_IS_DIR)) {
915                 g_free (saved_dir);
916                 saved_dir = NULL;
917         }
918         if (!g_file_test (default_dir, G_FILE_TEST_IS_DIR)) {
919                 default_dir = NULL;
920         }
921         pics_dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
922         if (pics_dir && !g_file_test (pics_dir, G_FILE_TEST_IS_DIR)) {
923                 pics_dir = NULL;
924         }
925
926         /* Set current dir to the last one or to DEFAULT_DIR or to home */
927         if (saved_dir) {
928                 gtk_file_chooser_set_current_folder (chooser_dialog, saved_dir);
929         }
930         else if (pics_dir) {
931                 gtk_file_chooser_set_current_folder (chooser_dialog, pics_dir);
932         }
933         else if (default_dir) {
934                 gtk_file_chooser_set_current_folder (chooser_dialog, default_dir);
935         } else {
936                 gtk_file_chooser_set_current_folder (chooser_dialog, g_get_home_dir ());
937         }
938
939         /* Add shortcuts to special dirs */
940         if (saved_dir) {
941                 gtk_file_chooser_add_shortcut_folder (chooser_dialog, saved_dir, NULL);
942         }
943         else if (pics_dir) {
944                 gtk_file_chooser_add_shortcut_folder (chooser_dialog, pics_dir, NULL);
945         }
946         if (default_dir) {
947                 gtk_file_chooser_add_shortcut_folder (chooser_dialog, default_dir, NULL);
948         }
949
950         /* Setup preview image */
951         image = gtk_image_new ();
952         gtk_file_chooser_set_preview_widget (chooser_dialog, image);
953         gtk_widget_set_size_request (image, AVATAR_SIZE_SAVE, AVATAR_SIZE_SAVE);
954         gtk_widget_show (image);
955         gtk_file_chooser_set_use_preview_label (chooser_dialog, FALSE);
956         g_signal_connect (chooser_dialog, "update-preview",
957                           G_CALLBACK (avatar_chooser_update_preview_cb),
958                           self);
959
960         /* Setup filers */
961         filter = gtk_file_filter_new ();
962         gtk_file_filter_set_name (filter, _("Images"));
963         gtk_file_filter_add_pixbuf_formats (filter);
964         gtk_file_chooser_add_filter (chooser_dialog, filter);
965         filter = gtk_file_filter_new ();
966         gtk_file_filter_set_name (filter, _("All Files"));
967         gtk_file_filter_add_pattern (filter, "*");
968         gtk_file_chooser_add_filter (chooser_dialog, filter);
969
970         /* Setup response */
971         gtk_dialog_set_default_response (GTK_DIALOG (chooser_dialog), EMPATHY_AVATAR_CHOOSER_RESPONSE_FILE);
972         g_signal_connect (chooser_dialog, "response",
973                           G_CALLBACK (avatar_chooser_response_cb),
974                           self);
975
976         gtk_widget_show (GTK_WIDGET (chooser_dialog));
977
978         g_free (saved_dir);
979 }
980
981 static void
982 empathy_avatar_chooser_init (EmpathyAvatarChooser *self)
983 {
984         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
985                 EMPATHY_TYPE_AVATAR_CHOOSER, EmpathyAvatarChooserPrivate);
986
987         gtk_drag_dest_set (GTK_WIDGET (self),
988                            GTK_DEST_DEFAULT_ALL,
989                            drop_types,
990                            G_N_ELEMENTS (drop_types),
991                            GDK_ACTION_COPY);
992
993         self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
994
995         g_signal_connect (self, "drag-motion",
996                           G_CALLBACK (avatar_chooser_drag_motion_cb),
997                           self);
998         g_signal_connect (self, "drag-leave",
999                           G_CALLBACK (avatar_chooser_drag_leave_cb),
1000                           self);
1001         g_signal_connect (self, "drag-drop",
1002                           G_CALLBACK (avatar_chooser_drag_drop_cb),
1003                           self);
1004         g_signal_connect (self, "drag-data-received",
1005                           G_CALLBACK (avatar_chooser_drag_data_received_cb),
1006                           self);
1007         g_signal_connect (self, "clicked",
1008                           G_CALLBACK (avatar_chooser_clicked_cb),
1009                           self);
1010
1011         empathy_avatar_chooser_set (self, NULL);
1012 }
1013
1014 static void
1015 avatar_chooser_set_image_from_avatar (EmpathyAvatarChooser *self,
1016                                       EmpathyAvatar        *avatar,
1017                                       gboolean              set_locally)
1018 {
1019         GdkPixbuf *pixbuf;
1020         gchar     *mime_type = NULL;
1021
1022         g_assert (avatar != NULL);
1023
1024         pixbuf = empathy_pixbuf_from_data_and_mime ((gchar *) avatar->data,
1025                                                     avatar->len,
1026                                                     &mime_type);
1027         if (pixbuf == NULL) {
1028                 DEBUG ("couldn't make a pixbuf from avatar; giving up");
1029                 return;
1030         }
1031
1032         if (avatar->format == NULL) {
1033                 avatar->format = mime_type;
1034         } else {
1035                 if (strcmp (mime_type, avatar->format)) {
1036                         DEBUG ("avatar->format is %s; gdkpixbuf yields %s!",
1037                                 avatar->format, mime_type);
1038                 }
1039                 g_free (mime_type);
1040         }
1041
1042         empathy_avatar_ref (avatar);
1043
1044         avatar_chooser_set_image (self, avatar, pixbuf, set_locally);
1045 }
1046
1047 /**
1048  * empathy_avatar_chooser_new:
1049  *
1050  * Creates a new #EmpathyAvatarChooser.
1051  *
1052  * Return value: a new #EmpathyAvatarChooser
1053  */
1054 GtkWidget *
1055 empathy_avatar_chooser_new (void)
1056 {
1057         return g_object_new (EMPATHY_TYPE_AVATAR_CHOOSER, NULL);
1058 }
1059
1060 /**
1061  * empathy_avatar_chooser_set:
1062  * @self: an #EmpathyAvatarChooser
1063  * @avatar: a new #EmpathyAvatar
1064  *
1065  * Sets the @self to display the avatar indicated by @avatar.
1066  */
1067 void
1068 empathy_avatar_chooser_set (EmpathyAvatarChooser *self,
1069                             EmpathyAvatar        *avatar)
1070 {
1071         g_return_if_fail (EMPATHY_IS_AVATAR_CHOOSER (self));
1072
1073         if (avatar != NULL) {
1074                 avatar_chooser_set_image_from_avatar (self, avatar, FALSE);
1075         } else {
1076                 avatar_chooser_clear_image (self);
1077         }
1078 }
1079
1080 /**
1081  * empathy_avatar_chooser_get_image_data:
1082  * @self: an #EmpathyAvatarChooser
1083  * @data: avatar bytes
1084  * @data_size: size of @data
1085  * @mime_type: avatar mime-type
1086  *
1087  * Gets image data about the currently selected avatar.
1088  */
1089 void
1090 empathy_avatar_chooser_get_image_data (EmpathyAvatarChooser  *self,
1091                                        const gchar          **data,
1092                                        gsize                 *data_size,
1093                                        const gchar          **mime_type)
1094 {
1095         g_return_if_fail (EMPATHY_IS_AVATAR_CHOOSER (self));
1096
1097         if (self->priv->avatar != NULL) {
1098                 if (data != NULL) {
1099                         *data = (gchar *) self->priv->avatar->data;
1100                 }
1101                 if (data_size != NULL) {
1102                         *data_size = self->priv->avatar->len;
1103                 }
1104                 if (mime_type != NULL) {
1105                         *mime_type = self->priv->avatar->format;
1106                 }
1107         } else {
1108                 if (data != NULL) {
1109                         *data = NULL;
1110                 }
1111                 if (data_size != NULL) {
1112                         *data_size = 0;
1113                 }
1114                 if (mime_type != NULL) {
1115                         *mime_type = NULL;
1116                 }
1117         }
1118 }
1119
1120 void
1121 empathy_avatar_chooser_set_account (EmpathyAvatarChooser *self,
1122                                        TpAccount *account)
1123 {
1124         g_return_if_fail (account != NULL);
1125
1126         avatar_chooser_set_connection (self, tp_account_get_connection (account));
1127         g_object_notify (G_OBJECT (self), "connection");
1128 }