]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-theme-boxes.c
Merge commit 'staz/dnd'
[empathy.git] / libempathy-gtk / empathy-theme-boxes.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Imendio AB
4  * Copyright (C) 2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: 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
31 #include <telepathy-glib/util.h>
32
33 #include <libempathy/empathy-utils.h>
34 #include "empathy-theme-boxes.h"
35 #include "empathy-ui-utils.h"
36 #include "empathy-conf.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
39 #include <libempathy/empathy-debug.h>
40
41 #define MARGIN 4
42 #define HEADER_PADDING 2
43
44 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeBoxes)
45 typedef struct {
46         gboolean show_avatars;
47         guint    notify_show_avatars_id;
48 } EmpathyThemeBoxesPriv;
49
50 G_DEFINE_TYPE (EmpathyThemeBoxes, empathy_theme_boxes, EMPATHY_TYPE_CHAT_TEXT_VIEW);
51
52 static void
53 theme_boxes_create_tags (EmpathyThemeBoxes *theme)
54 {
55         GtkTextBuffer *buffer;
56
57         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (theme));
58
59         gtk_text_buffer_create_tag (buffer, EMPATHY_THEME_BOXES_TAG_HEADER,
60                                     "pixels-above-lines", HEADER_PADDING,
61                                     "pixels-below-lines", HEADER_PADDING,
62                                     NULL);
63
64         gtk_text_buffer_create_tag (buffer, EMPATHY_THEME_BOXES_TAG_HEADER_LINE, NULL);
65 }
66
67 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
68  * pixbuf. Returns a new ref.
69  */
70 static GdkPixbuf *
71 theme_boxes_pad_to_size (GdkPixbuf *pixbuf,
72                          gint       width,
73                          gint       height,
74                          gint       extra_padding_right)
75 {
76         gint       src_width, src_height;
77         GdkPixbuf *padded;
78         gint       x_offset, y_offset;
79
80         src_width = gdk_pixbuf_get_width (pixbuf);
81         src_height = gdk_pixbuf_get_height (pixbuf);
82
83         x_offset = (width - src_width) / 2;
84         y_offset = (height - src_height) / 2;
85
86         padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
87                                  TRUE, /* alpha */
88                                  gdk_pixbuf_get_bits_per_sample (pixbuf),
89                                  width + extra_padding_right,
90                                  height);
91
92         gdk_pixbuf_fill (padded, 0);
93
94         gdk_pixbuf_copy_area (pixbuf,
95                               0, /* source coords */
96                               0,
97                               src_width,
98                               src_height,
99                               padded,
100                               x_offset, /* dest coords */
101                               y_offset);
102
103         return padded;
104 }
105
106 typedef struct {
107         GdkPixbuf *pixbuf;
108         gchar     *token;
109 } AvatarData;
110
111 static void
112 theme_boxes_avatar_cache_data_free (gpointer ptr)
113 {
114         AvatarData *data = ptr;
115
116         g_object_unref (data->pixbuf);
117         g_free (data->token);
118         g_slice_free (AvatarData, data);
119 }
120
121 static GdkPixbuf *
122 theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
123 {
124         AvatarData        *data;
125         EmpathyAvatar     *avatar;
126         GdkPixbuf         *tmp_pixbuf;
127         GdkPixbuf         *pixbuf = NULL;
128
129         /* Check if avatar is in cache and if it's up to date */
130         avatar = empathy_contact_get_avatar (contact);
131         data = g_object_get_data (G_OBJECT (contact), "chat-view-avatar-cache");
132         if (data) {
133                 if (avatar && !tp_strdiff (avatar->token, data->token)) {
134                         /* We have the avatar in cache */
135                         return data->pixbuf;
136                 }
137         }
138
139         /* Avatar not in cache, create pixbuf */
140         tmp_pixbuf = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
141         if (tmp_pixbuf) {
142                 pixbuf = theme_boxes_pad_to_size (tmp_pixbuf, 32, 32, 6);
143                 g_object_unref (tmp_pixbuf);
144         }
145         if (!pixbuf) {
146                 return NULL;
147         }
148
149         /* Insert new pixbuf in cache */
150         data = g_slice_new0 (AvatarData);
151         data->token = g_strdup (avatar->token);
152         data->pixbuf = pixbuf;
153
154         g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
155                                 data, theme_boxes_avatar_cache_data_free);
156
157         return data->pixbuf;
158 }
159
160 static void
161 table_size_allocate_cb (GtkWidget     *view,
162                         GtkAllocation *allocation,
163                         GtkWidget     *box)
164 {
165         gint width, height;
166
167         gtk_widget_get_size_request (box, NULL, &height);
168
169         width = allocation->width;
170
171         width -= \
172                 gtk_text_view_get_right_margin (GTK_TEXT_VIEW (view)) - \
173                 gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view));
174         width -= 2 * MARGIN;
175         width -= 2 * HEADER_PADDING;
176
177         gtk_widget_set_size_request (box, width, height);
178 }
179
180 static void
181 theme_boxes_maybe_append_header (EmpathyThemeBoxes *theme,
182                                  EmpathyMessage    *msg)
183 {
184         EmpathyChatTextView  *view = EMPATHY_CHAT_TEXT_VIEW (theme);
185         EmpathyThemeBoxesPriv*priv = GET_PRIV (theme);
186         EmpathyContact       *contact;
187         EmpathyContact       *last_contact;
188         GdkPixbuf            *avatar = NULL;
189         GtkTextBuffer        *buffer;
190         const gchar          *name;
191         GtkTextIter           iter;
192         GtkWidget            *label1, *label2;
193         GtkTextChildAnchor   *anchor;
194         GtkWidget            *box;
195         gchar                *str;
196         time_t                time_;
197         gchar                *tmp;
198         GtkTextIter           start;
199         gboolean              color_set;
200         GtkTextTagTable      *table;
201         GtkTextTag           *tag;
202         GString              *str_obj;
203
204         contact = empathy_message_get_sender (msg);
205         name = empathy_contact_get_name (contact);
206         last_contact = empathy_chat_text_view_get_last_contact (view);
207         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (theme));
208
209         DEBUG ("Maybe add fancy header");
210
211         /* Only insert a header if the previously inserted block is not the same
212          * as this one.
213          */
214         if (empathy_contact_equal (last_contact, contact)) {
215                 return;
216         }
217
218         empathy_chat_text_view_append_spacing (view);
219
220         /* Insert header line */
221         gtk_text_buffer_get_end_iter (buffer, &iter);
222         gtk_text_buffer_insert_with_tags_by_name (buffer,
223                                                   &iter,
224                                                   "\n",
225                                                   -1,
226                                                   EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
227                                                   NULL);
228
229         gtk_text_buffer_get_end_iter (buffer, &iter);
230         anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
231
232         /* Create a hbox for the header and resize it when the view allocation
233          * changes */
234         box = gtk_hbox_new (FALSE, 0);
235         g_signal_connect_object (view, "size-allocate",
236                                  G_CALLBACK (table_size_allocate_cb),
237                                  box, 0);
238
239         /* Add avatar to the box if needed */
240         if (priv->show_avatars) {
241                 avatar = theme_boxes_get_avatar_pixbuf_with_cache (contact);
242                 if (avatar) {
243                         GtkWidget *image;
244
245                         image = gtk_image_new_from_pixbuf (avatar);
246
247                         gtk_box_pack_start (GTK_BOX (box), image,
248                                             FALSE, TRUE, 2);
249                 }
250         }
251
252         /* Add contact alias */
253         str = g_markup_printf_escaped ("<b>%s</b>", name);
254         label1 = g_object_new (GTK_TYPE_LABEL,
255                                "label", str,
256                                "use-markup", TRUE,
257                                "xalign", 0.0,
258                                NULL);
259         g_free (str);
260
261         /* Add the message receive time */
262         time_ = empathy_message_get_timestamp (msg);
263         tmp = empathy_time_to_string_local (time_,
264                                            EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
265         str = g_strdup_printf ("<i>%s</i>", tmp);
266         label2 = g_object_new (GTK_TYPE_LABEL,
267                                "label", str,
268                                "use-markup", TRUE,
269                                "xalign", 1.0,
270                                NULL);
271
272         str_obj = g_string_new ("\n- ");
273         g_string_append (str_obj, name);
274         g_string_append (str_obj, ", ");
275         g_string_append (str_obj, tmp);
276         g_string_append (str_obj, " -");
277         g_free (tmp);
278         g_free (str);
279
280         /* Set foreground color of labels to the same color than the header tag. */
281         table = gtk_text_buffer_get_tag_table (buffer);
282         tag = gtk_text_tag_table_lookup (table, EMPATHY_THEME_BOXES_TAG_HEADER);
283         g_object_get (tag, "foreground-set", &color_set, NULL);
284         if (color_set) {
285                 GdkColor *color;
286
287                 g_object_get (tag, "foreground-gdk", &color, NULL);
288                 gtk_widget_modify_fg (label1, GTK_STATE_NORMAL, color);
289                 gtk_widget_modify_fg (label2, GTK_STATE_NORMAL, color);
290                 gdk_color_free (color);
291         }
292
293         /* Pack labels into the box */
294         gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.5);
295         gtk_misc_set_alignment (GTK_MISC (label2), 1.0, 0.5);
296         gtk_box_pack_start (GTK_BOX (box), label1, TRUE, TRUE, 0);
297         gtk_box_pack_start (GTK_BOX (box), label2, TRUE, TRUE, 0);
298
299         /* Add the header box to the text view */
300         g_object_set_data_full (G_OBJECT (box),
301                                 "str_obj",
302                                 g_string_free (str_obj, FALSE),
303                                 g_free);
304         gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view),
305                                            box,
306                                            anchor);
307         gtk_widget_show_all (box);
308
309         /* Insert a header line */
310         gtk_text_buffer_get_end_iter (buffer, &iter);
311         start = iter;
312         gtk_text_iter_backward_char (&start);
313         gtk_text_buffer_apply_tag_by_name (buffer,
314                                            EMPATHY_THEME_BOXES_TAG_HEADER,
315                                            &start, &iter);
316         gtk_text_buffer_insert_with_tags_by_name (buffer,
317                                                   &iter,
318                                                   "\n",
319                                                   -1,
320                                                   EMPATHY_THEME_BOXES_TAG_HEADER,
321                                                   NULL);
322         gtk_text_buffer_get_end_iter (buffer, &iter);
323         gtk_text_buffer_insert_with_tags_by_name (buffer,
324                                                   &iter,
325                                                   "\n",
326                                                   -1,
327                                                   EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
328                                                   NULL);
329 }
330
331 static void
332 theme_boxes_append_message (EmpathyChatTextView *view,
333                             EmpathyMessage      *message)
334 {
335         EmpathyContact *sender;
336
337         theme_boxes_maybe_append_header (EMPATHY_THEME_BOXES (view), message);
338
339         sender = empathy_message_get_sender (message);
340         if (empathy_message_get_tptype (message) ==
341             TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
342                 gchar *body;
343
344                 body = g_strdup_printf (" * %s %s",
345                                         empathy_contact_get_name (sender),
346                                         empathy_message_get_body (message));
347                 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
348                                                     body,
349                                                     EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION);
350         } else {
351                 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
352                                                     empathy_message_get_body (message),
353                                                     EMPATHY_CHAT_TEXT_VIEW_TAG_BODY);
354         }
355 }
356
357 static void
358 theme_boxes_notify_show_avatars_cb (EmpathyConf *conf,
359                                     const gchar *key,
360                                     gpointer     user_data)
361 {
362         EmpathyThemeBoxesPriv *priv = GET_PRIV (user_data);
363
364         empathy_conf_get_bool (conf, key, &priv->show_avatars);
365 }
366
367 static void
368 theme_boxes_finalize (GObject *object)
369 {
370         EmpathyThemeBoxesPriv *priv = GET_PRIV (object);
371
372         empathy_conf_notify_remove (empathy_conf_get (),
373                                     priv->notify_show_avatars_id);
374
375         G_OBJECT_CLASS (empathy_theme_boxes_parent_class)->finalize (object);
376 }
377
378 static void
379 empathy_theme_boxes_class_init (EmpathyThemeBoxesClass *class)
380 {
381         GObjectClass             *object_class;
382         EmpathyChatTextViewClass *chat_text_view_class;
383
384         object_class = G_OBJECT_CLASS (class);
385         chat_text_view_class  = EMPATHY_CHAT_TEXT_VIEW_CLASS (class);
386
387         object_class->finalize = theme_boxes_finalize;
388         chat_text_view_class->append_message = theme_boxes_append_message;
389
390         g_type_class_add_private (object_class, sizeof (EmpathyThemeBoxesPriv));
391 }
392
393 static void
394 empathy_theme_boxes_init (EmpathyThemeBoxes *theme)
395 {
396         EmpathyThemeBoxesPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
397                 EMPATHY_TYPE_THEME_BOXES, EmpathyThemeBoxesPriv);
398
399         theme->priv = priv;
400
401         theme_boxes_create_tags (theme);
402
403         priv->notify_show_avatars_id =
404                 empathy_conf_notify_add (empathy_conf_get (),
405                                          EMPATHY_PREFS_UI_SHOW_AVATARS,
406                                          theme_boxes_notify_show_avatars_cb,
407                                          theme);
408
409         empathy_conf_get_bool (empathy_conf_get (),
410                                EMPATHY_PREFS_UI_SHOW_AVATARS,
411                                &priv->show_avatars);
412
413         /* Define margin */
414         g_object_set (theme,
415                       "left-margin", MARGIN,
416                       "right-margin", MARGIN,
417                       NULL);
418 }
419
420 EmpathyThemeBoxes *
421 empathy_theme_boxes_new (void)
422 {
423         return g_object_new (EMPATHY_TYPE_THEME_BOXES,
424                              "only-if-date", TRUE,
425                              NULL);
426 }
427