1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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>
28 #include <glib/gi18n-lib.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>
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)
48 gboolean show_avatars
;
49 } EmpathyThemeBoxesPriv
;
51 G_DEFINE_TYPE (EmpathyThemeBoxes
, empathy_theme_boxes
, EMPATHY_TYPE_CHAT_TEXT_VIEW
);
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
,
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.
72 theme_boxes_pad_to_size (GdkPixbuf
*pixbuf
,
75 gint extra_padding_right
)
77 gint src_width
, src_height
;
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
),
89 gdk_pixbuf_get_bits_per_sample (pixbuf
),
90 width
+ extra_padding_right
,
93 gdk_pixbuf_fill (padded
, 0);
95 gdk_pixbuf_copy_area (pixbuf
,
96 0, /* source coords */
101 x_offset
, /* dest coords */
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
);
123 theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact
*contact
)
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");
134 if (avatar
&& !tp_strdiff (avatar
->filename
, data
->filename
)) {
135 /* We have the avatar in cache */
140 /* Avatar not in cache, create pixbuf */
141 tmp_pixbuf
= empathy_pixbuf_avatar_from_contact_scaled (contact
, 32, 32);
143 pixbuf
= theme_boxes_pad_to_size (tmp_pixbuf
, 32, 32, 6);
144 g_object_unref (tmp_pixbuf
);
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
);
164 table_size_allocate_cb (GtkWidget
*view
,
165 GtkAllocation
*allocation
,
170 gtk_widget_get_size_request (box
, NULL
, &height
);
172 width
= allocation
->width
;
175 gtk_text_view_get_right_margin (GTK_TEXT_VIEW (view
)) - \
176 gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view
));
178 width
-= 2 * HEADER_PADDING
;
180 gtk_widget_set_size_request (box
, width
, height
);
184 theme_boxes_maybe_append_header (EmpathyThemeBoxes
*theme
,
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
;
195 GtkWidget
*label1
, *label2
;
196 GtkTextChildAnchor
*anchor
;
203 GtkTextTagTable
*table
;
206 gboolean consecutive
;
208 contact
= empathy_message_get_sender (msg
);
209 name
= empathy_contact_get_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
) {
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
,
234 EMPATHY_THEME_BOXES_TAG_HEADER_LINE
,
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
242 box
= gtk_hbox_new (FALSE
, 0);
243 g_signal_connect_object (view
, "size-allocate",
244 G_CALLBACK (table_size_allocate_cb
),
247 /* Add avatar to the box if needed */
248 if (priv
->show_avatars
) {
249 avatar
= theme_boxes_get_avatar_pixbuf_with_cache (contact
);
253 image
= gtk_image_new_from_pixbuf (avatar
);
255 gtk_box_pack_start (GTK_BOX (box
), image
,
260 /* Add contact alias */
261 str
= g_markup_printf_escaped ("<b>%s</b>", name
);
262 label1
= g_object_new (GTK_TYPE_LABEL
,
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
,
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
, " -");
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
);
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
),
309 g_string_free (str_obj
, FALSE
),
311 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view
),
314 gtk_widget_show_all (box
);
316 /* Insert a header line */
317 gtk_text_buffer_get_end_iter (buffer
, &iter
);
319 gtk_text_iter_backward_char (&start
);
320 gtk_text_buffer_apply_tag_by_name (buffer
,
321 EMPATHY_THEME_BOXES_TAG_HEADER
,
323 gtk_text_buffer_insert_with_tags_by_name (buffer
,
327 EMPATHY_THEME_BOXES_TAG_HEADER
,
329 gtk_text_buffer_get_end_iter (buffer
, &iter
);
330 gtk_text_buffer_insert_with_tags_by_name (buffer
,
334 EMPATHY_THEME_BOXES_TAG_HEADER_LINE
,
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
) {
351 body
= g_strdup_printf (" * %s %s",
352 empathy_contact_get_alias (sender
),
353 empathy_message_get_body (message
));
354 empathy_chat_text_view_append_body (EMPATHY_CHAT_TEXT_VIEW (view
),
356 EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION
);
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
);
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
));
379 empathy_theme_boxes_init (EmpathyThemeBoxes
*theme
)
381 EmpathyThemeBoxesPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (theme
,
382 EMPATHY_TYPE_THEME_BOXES
, EmpathyThemeBoxesPriv
);
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
);
394 "left-margin", MARGIN
,
395 "right-margin", MARGIN
,
400 empathy_theme_boxes_new (void)
402 return g_object_new (EMPATHY_TYPE_THEME_BOXES
,
403 "only-if-date", TRUE
,