Updated POTFILES.in
[empathy-mirror.git] / libempathy-gtk / empathy-theme-boxes.c
blobb9fd08d84b27e5738fcb05d753a283d980e0473c
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.
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.
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.
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
21 * Authors: Xavier Claessens <xclaesse@gmail.com>
24 #include <config.h>
26 #include <string.h>
28 #include <glib/gi18n-lib.h>
29 #include <gtk/gtk.h>
31 #include <telepathy-glib/util.h>
33 #include <libempathy/empathy-utils.h>
34 #include "empathy-theme-boxes.h"
35 #include "empathy-ui-utils.h"
37 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
38 #include <libempathy/empathy-debug.h>
40 #define MARGIN 4
41 #define HEADER_PADDING 2
43 /* "Join" consecutive messages with timestamps within five minutes */
44 #define MESSAGE_JOIN_PERIOD 5*60
46 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeBoxes)
47 typedef struct {
48 gboolean show_avatars;
49 } EmpathyThemeBoxesPriv;
51 G_DEFINE_TYPE (EmpathyThemeBoxes, empathy_theme_boxes, EMPATHY_TYPE_CHAT_TEXT_VIEW);
53 static void
54 theme_boxes_create_tags (EmpathyThemeBoxes *theme)
56 GtkTextBuffer *buffer;
58 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (theme));
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);
65 gtk_text_buffer_create_tag (buffer, EMPATHY_THEME_BOXES_TAG_HEADER_LINE, NULL);
68 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
69 * pixbuf. Returns a new ref.
71 static GdkPixbuf *
72 theme_boxes_pad_to_size (GdkPixbuf *pixbuf,
73 gint width,
74 gint height,
75 gint extra_padding_right)
77 gint src_width, src_height;
78 GdkPixbuf *padded;
79 gint x_offset, y_offset;
81 src_width = gdk_pixbuf_get_width (pixbuf);
82 src_height = gdk_pixbuf_get_height (pixbuf);
84 x_offset = (width - src_width) / 2;
85 y_offset = (height - src_height) / 2;
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);
93 gdk_pixbuf_fill (padded, 0);
95 gdk_pixbuf_copy_area (pixbuf,
96 0, /* source coords */
98 src_width,
99 src_height,
100 padded,
101 x_offset, /* dest coords */
102 y_offset);
104 return padded;
107 typedef struct {
108 GdkPixbuf *pixbuf;
109 gchar *filename;
110 } AvatarData;
112 static void
113 theme_boxes_avatar_cache_data_free (gpointer ptr)
115 AvatarData *data = ptr;
117 g_object_unref (data->pixbuf);
118 g_free (data->filename);
119 g_slice_free (AvatarData, data);
122 static GdkPixbuf *
123 theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
125 AvatarData *data;
126 EmpathyAvatar *avatar;
127 GdkPixbuf *tmp_pixbuf;
128 GdkPixbuf *pixbuf = NULL;
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;
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);
146 if (!pixbuf) {
147 return NULL;
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;
157 g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
158 data, theme_boxes_avatar_cache_data_free);
160 return data->pixbuf;
163 static void
164 table_size_allocate_cb (GtkWidget *view,
165 GtkAllocation *allocation,
166 GtkWidget *box)
168 gint width, height;
170 gtk_widget_get_size_request (box, NULL, &height);
172 width = allocation->width;
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;
180 gtk_widget_set_size_request (box, width, height);
183 static void
184 theme_boxes_maybe_append_header (EmpathyThemeBoxes *theme,
185 EmpathyMessage *msg)
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;
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);
216 DEBUG ("Maybe add fancy header");
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
222 if (empathy_contact_equal (last_contact, contact) && consecutive) {
223 return;
226 empathy_chat_text_view_append_spacing (view);
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",
234 EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
235 NULL);
237 gtk_text_buffer_get_end_iter (buffer, &iter);
238 anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
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);
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;
253 image = gtk_image_new_from_pixbuf (avatar);
255 gtk_box_pack_start (GTK_BOX (box), image,
256 FALSE, TRUE, 2);
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);
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);
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);
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;
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);
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);
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);
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",
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",
334 EMPATHY_THEME_BOXES_TAG_HEADER_LINE,
335 NULL);
338 static void
339 theme_boxes_append_message (EmpathyChatTextView *view,
340 EmpathyMessage *message)
342 EmpathyContact *sender;
344 theme_boxes_maybe_append_header (EMPATHY_THEME_BOXES (view), message);
346 sender = empathy_message_get_sender (message);
347 if (empathy_message_get_tptype (message) ==
348 TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
349 gchar *body;
351 body = g_strdup_printf (" * %s %s",
352 empathy_contact_get_logged_alias (sender),
353 empathy_message_get_body (message));
354 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
355 body,
356 EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION);
357 } else {
358 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view),
359 empathy_message_get_body (message),
360 EMPATHY_CHAT_TEXT_VIEW_TAG_BODY);
364 static void
365 empathy_theme_boxes_class_init (EmpathyThemeBoxesClass *class)
367 GObjectClass *object_class;
368 EmpathyChatTextViewClass *chat_text_view_class;
370 object_class = G_OBJECT_CLASS (class);
371 chat_text_view_class = EMPATHY_CHAT_TEXT_VIEW_CLASS (class);
373 chat_text_view_class->append_message = theme_boxes_append_message;
375 g_type_class_add_private (object_class, sizeof (EmpathyThemeBoxesPriv));
378 static void
379 empathy_theme_boxes_init (EmpathyThemeBoxes *theme)
381 EmpathyThemeBoxesPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
382 EMPATHY_TYPE_THEME_BOXES, EmpathyThemeBoxesPriv);
384 theme->priv = priv;
386 /* This is just hard-coded to TRUE until someone adds a tickybox in the
387 * Theme tab for it. */
388 priv->show_avatars = TRUE;
390 theme_boxes_create_tags (theme);
392 /* Define margin */
393 g_object_set (theme,
394 "left-margin", MARGIN,
395 "right-margin", MARGIN,
396 NULL);
399 EmpathyThemeBoxes *
400 empathy_theme_boxes_new (void)
402 return g_object_new (EMPATHY_TYPE_THEME_BOXES,
403 "only-if-date", TRUE,
404 NULL);