]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-theme-boxes.c
use the 48x48 version of the local-xmpp icon
[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
37 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
38 #include <libempathy/empathy-debug.h>
39
40 #define MARGIN 4
41 #define HEADER_PADDING 2
42
43 /* "Join" consecutive messages with timestamps within five minutes */
44 #define MESSAGE_JOIN_PERIOD 5*60
45
46 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeBoxes)
47 typedef struct {
48         gboolean show_avatars;
49 } EmpathyThemeBoxesPriv;
50
51 G_DEFINE_TYPE (EmpathyThemeBoxes, empathy_theme_boxes, EMPATHY_TYPE_CHAT_TEXT_VIEW);
52
53 static void
54 theme_boxes_create_tags (EmpathyThemeBoxes *theme)
55 {
56         GtkTextBuffer *buffer;
57
58         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (theme));
59
60         gtk_text_buffer_create_tag (buffer, EMPATHY_THEME_BOXES_TAG_HEADER,
61                                     "pixels-above-lines", HEADER_PADDING,
62                                     "pixels-below-lines", HEADER_PADDING,
63                                     NULL);
64
65         gtk_text_buffer_create_tag (buffer, EMPATHY_THEME_BOXES_TAG_HEADER_LINE, NULL);
66 }
67
68 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
69  * pixbuf. Returns a new ref.
70  */
71 static GdkPixbuf *
72 theme_boxes_pad_to_size (GdkPixbuf *pixbuf,
73                          gint       width,
74                          gint       height,
75                          gint       extra_padding_right)
76 {
77         gint       src_width, src_height;
78         GdkPixbuf *padded;
79         gint       x_offset, y_offset;
80
81         src_width = gdk_pixbuf_get_width (pixbuf);
82         src_height = gdk_pixbuf_get_height (pixbuf);
83
84         x_offset = (width - src_width) / 2;
85         y_offset = (height - src_height) / 2;
86
87         padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
88                                  TRUE, /* alpha */
89                                  gdk_pixbuf_get_bits_per_sample (pixbuf),
90                                  width + extra_padding_right,
91                                  height);
92
93         gdk_pixbuf_fill (padded, 0);
94
95         gdk_pixbuf_copy_area (pixbuf,
96                               0, /* source coords */
97                               0,
98                               src_width,
99                               src_height,
100                               padded,
101                               x_offset, /* dest coords */
102                               y_offset);
103
104         return padded;
105 }
106
107 typedef struct {
108         GdkPixbuf *pixbuf;
109         gchar     *filename;
110 } AvatarData;
111
112 static void
113 theme_boxes_avatar_cache_data_free (gpointer ptr)
114 {
115         AvatarData *data = ptr;
116
117         g_object_unref (data->pixbuf);
118         g_free (data->filename);
119         g_slice_free (AvatarData, data);
120 }
121
122 static GdkPixbuf *
123 theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
124 {
125         AvatarData        *data;
126         EmpathyAvatar     *avatar;
127         GdkPixbuf         *tmp_pixbuf;
128         GdkPixbuf         *pixbuf = NULL;
129
130         /* Check if avatar is in cache and if it's up to date */
131         avatar = empathy_contact_get_avatar (contact);
132         data = g_object_get_data (G_OBJECT (contact), "chat-view-avatar-cache");
133         if (data) {
134                 if (avatar && !tp_strdiff (avatar->filename, data->filename)) {
135                         /* We have the avatar in cache */
136                         return data->pixbuf;
137                 }
138         }
139
140         /* Avatar not in cache, create pixbuf */
141         tmp_pixbuf = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
142         if (tmp_pixbuf) {
143                 pixbuf = theme_boxes_pad_to_size (tmp_pixbuf, 32, 32, 6);
144                 g_object_unref (tmp_pixbuf);
145         }
146         if (!pixbuf) {
147                 return NULL;
148         }
149
150         /* Insert new pixbuf in cache. We store the filename as it's unique
151          * for each version of an avatar, so we can use it to perform change
152          * detection (as above). */
153         data = g_slice_new0 (AvatarData);
154         data->filename = g_strdup (avatar->filename);
155         data->pixbuf = pixbuf;
156
157         g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
158                                 data, theme_boxes_avatar_cache_data_free);
159
160         return data->pixbuf;
161 }
162
163 static void
164 table_size_allocate_cb (GtkWidget     *view,
165                         GtkAllocation *allocation,
166                         GtkWidget     *box)
167 {
168         gint width, height;
169
170         gtk_widget_get_size_request (box, NULL, &height);
171
172         width = allocation->width;
173
174         width -= \
175                 gtk_text_view_get_right_margin (GTK_TEXT_VIEW (view)) - \
176                 gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view));
177         width -= 2 * MARGIN;
178         width -= 2 * HEADER_PADDING;
179
180         gtk_widget_set_size_request (box, width, height);
181 }
182
183 static void
184 theme_boxes_maybe_append_header (EmpathyThemeBoxes *theme,
185                                  EmpathyMessage    *msg)
186 {
187         EmpathyChatTextView  *view = EMPATHY_CHAT_TEXT_VIEW (theme);
188         EmpathyThemeBoxesPriv*priv = GET_PRIV (theme);
189         EmpathyContact       *contact;
190         EmpathyContact       *last_contact;
191         GdkPixbuf            *avatar = NULL;
192         GtkTextBuffer        *buffer;
193         const gchar          *name;
194         GtkTextIter           iter;
195         GtkWidget            *label1, *label2;
196         GtkTextChildAnchor   *anchor;
197         GtkWidget            *box;
198         gchar                *str;
199         gint64                time_;
200         gchar                *tmp;
201         GtkTextIter           start;
202         gboolean              color_set;
203         GtkTextTagTable      *table;
204         GtkTextTag           *tag;
205         GString              *str_obj;
206         gboolean              consecutive;
207
208         contact = empathy_message_get_sender (msg);
209         name = empathy_contact_get_logged_alias (contact);
210         last_contact = empathy_chat_text_view_get_last_contact (view);
211         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (theme));
212         time_ = empathy_message_get_timestamp (msg);
213         consecutive = (time_ - empathy_chat_text_view_get_last_timestamp (view)
214                 < MESSAGE_JOIN_PERIOD);
215
216         DEBUG ("Maybe add fancy header");
217
218         /* Only insert a header if
219          *   - the previously inserted block is not the same as this one.
220          *   - the delay between two messages is lower then MESSAGE_JOIN_PERIOD
221          */
222         if (empathy_contact_equal (last_contact, contact) && consecutive) {
223                 return;
224         }
225
226         empathy_chat_text_view_append_spacing (view);
227
228         /* Insert header line */
229         gtk_text_buffer_get_end_iter (buffer, &iter);
230         gtk_text_buffer_insert_with_tags_by_name (buffer,
231                                                   &iter,
232                                                   "\n",
233                                                   -1,
234                                                   EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
235                                                   NULL);
236
237         gtk_text_buffer_get_end_iter (buffer, &iter);
238         anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
239
240         /* Create a hbox for the header and resize it when the view allocation
241          * changes */
242         box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
243         g_signal_connect_object (view, "size-allocate",
244                                  G_CALLBACK (table_size_allocate_cb),
245                                  box, 0);
246
247         /* Add avatar to the box if needed */
248         if (priv->show_avatars) {
249                 avatar = theme_boxes_get_avatar_pixbuf_with_cache (contact);
250                 if (avatar) {
251                         GtkWidget *image;
252
253                         image = gtk_image_new_from_pixbuf (avatar);
254
255                         gtk_box_pack_start (GTK_BOX (box), image,
256                                             FALSE, TRUE, 2);
257                 }
258         }
259
260         /* Add contact alias */
261         str = g_markup_printf_escaped ("<b>%s</b>", name);
262         label1 = g_object_new (GTK_TYPE_LABEL,
263                                "label", str,
264                                "use-markup", TRUE,
265                                "xalign", 0.0,
266                                NULL);
267         g_free (str);
268
269         /* Add the message receive time */
270         tmp = empathy_time_to_string_local (time_,
271                                            EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
272         str = g_strdup_printf ("<i>%s</i>", tmp);
273         label2 = g_object_new (GTK_TYPE_LABEL,
274                                "label", str,
275                                "use-markup", TRUE,
276                                "xalign", 1.0,
277                                NULL);
278
279         str_obj = g_string_new ("\n- ");
280         g_string_append (str_obj, name);
281         g_string_append (str_obj, ", ");
282         g_string_append (str_obj, tmp);
283         g_string_append (str_obj, " -");
284         g_free (tmp);
285         g_free (str);
286
287         /* Set foreground color of labels to the same color than the header tag. */
288         table = gtk_text_buffer_get_tag_table (buffer);
289         tag = gtk_text_tag_table_lookup (table, EMPATHY_THEME_BOXES_TAG_HEADER);
290         g_object_get (tag, "foreground-set", &color_set, NULL);
291         if (color_set) {
292                 GdkColor *color;
293
294                 g_object_get (tag, "foreground-gdk", &color, NULL);
295                 gtk_widget_modify_fg (label1, GTK_STATE_NORMAL, color);
296                 gtk_widget_modify_fg (label2, GTK_STATE_NORMAL, color);
297                 gdk_color_free (color);
298         }
299
300         /* Pack labels into the box */
301         gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.5);
302         gtk_misc_set_alignment (GTK_MISC (label2), 1.0, 0.5);
303         gtk_box_pack_start (GTK_BOX (box), label1, TRUE, TRUE, 0);
304         gtk_box_pack_start (GTK_BOX (box), label2, TRUE, TRUE, 0);
305
306         /* Add the header box to the text view */
307         g_object_set_data_full (G_OBJECT (box),
308                                 "str_obj",
309                                 g_string_free (str_obj, FALSE),
310                                 g_free);
311         gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view),
312                                            box,
313                                            anchor);
314         gtk_widget_show_all (box);
315
316         /* Insert a header line */
317         gtk_text_buffer_get_end_iter (buffer, &iter);
318         start = iter;
319         gtk_text_iter_backward_char (&start);
320         gtk_text_buffer_apply_tag_by_name (buffer,
321                                            EMPATHY_THEME_BOXES_TAG_HEADER,
322                                            &start, &iter);
323         gtk_text_buffer_insert_with_tags_by_name (buffer,
324                                                   &iter,
325                                                   "\n",
326                                                   -1,
327                                                   EMPATHY_THEME_BOXES_TAG_HEADER,
328                                                   NULL);
329         gtk_text_buffer_get_end_iter (buffer, &iter);
330         gtk_text_buffer_insert_with_tags_by_name (buffer,
331                                                   &iter,
332                                                   "\n",
333                                                   -1,
334                                                   EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
335                                                   NULL);
336 }
337
338 static void
339 theme_boxes_append_message (EmpathyChatTextView *view,
340                             EmpathyMessage      *message,
341                             gboolean             should_highlight)
342 {
343         EmpathyContact *sender;
344
345         theme_boxes_maybe_append_header (EMPATHY_THEME_BOXES (view), message);
346
347         sender = empathy_message_get_sender (message);
348         if (empathy_message_get_tptype (message) ==
349             TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
350                 gchar *body;
351
352                 body = g_strdup_printf (" * %s %s",
353                                         empathy_contact_get_logged_alias (sender),
354                                         empathy_message_get_body (message));
355                 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
356                                                     body,
357                                                     EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION);
358         } else {
359                 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
360                                                     empathy_message_get_body (message),
361                                                     EMPATHY_CHAT_TEXT_VIEW_TAG_BODY);
362         }
363 }
364
365 static void
366 empathy_theme_boxes_class_init (EmpathyThemeBoxesClass *class)
367 {
368         GObjectClass             *object_class;
369         EmpathyChatTextViewClass *chat_text_view_class;
370
371         object_class = G_OBJECT_CLASS (class);
372         chat_text_view_class  = EMPATHY_CHAT_TEXT_VIEW_CLASS (class);
373
374         chat_text_view_class->append_message = theme_boxes_append_message;
375
376         g_type_class_add_private (object_class, sizeof (EmpathyThemeBoxesPriv));
377 }
378
379 static void
380 empathy_theme_boxes_init (EmpathyThemeBoxes *theme)
381 {
382         EmpathyThemeBoxesPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
383                 EMPATHY_TYPE_THEME_BOXES, EmpathyThemeBoxesPriv);
384
385         theme->priv = priv;
386
387         /* This is just hard-coded to TRUE until someone adds a tickybox in the
388          * Theme tab for it. */
389         priv->show_avatars = TRUE;
390
391         theme_boxes_create_tags (theme);
392
393         /* Define margin */
394         g_object_set (theme,
395                       "left-margin", MARGIN,
396                       "right-margin", MARGIN,
397                       NULL);
398 }
399
400 EmpathyThemeBoxes *
401 empathy_theme_boxes_new (void)
402 {
403         return g_object_new (EMPATHY_TYPE_THEME_BOXES,
404                              "only-if-date", TRUE,
405                              NULL);
406 }
407