1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
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.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
25 #include <glib/gi18n-lib.h>
28 #include <telepathy-glib/util.h>
29 #include <libempathy/empathy-utils.h>
30 #include "empathy-ui-utils.h"
31 #include "empathy-theme-boxes.h"
33 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
34 #include <libempathy/empathy-debug.h>
37 #define HEADER_PADDING 2
39 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeBoxes)
41 gchar *header_foreground;
42 gchar *header_background;
43 gchar *header_line_background;
44 gchar *text_foreground;
45 gchar *text_background;
46 gchar *action_foreground;
47 gchar *highlight_foreground;
48 gchar *time_foreground;
49 gchar *event_foreground;
50 gchar *invite_foreground;
51 gchar *link_foreground;
52 } EmpathyThemeBoxesPriv;
54 static void theme_boxes_finalize (GObject *object);
55 static void theme_boxes_get_property (GObject *object,
59 static void theme_boxes_set_property (GObject *object,
63 static void theme_boxes_define_theme_tags (EmpathyTheme *theme,
64 EmpathyChatView *view);
65 static void theme_boxes_update_view (EmpathyTheme *theme,
66 EmpathyChatView *view);
67 static void theme_boxes_append_message (EmpathyTheme *theme,
68 EmpathyChatView *view,
69 EmpathyMessage *message);
70 static void theme_boxes_append_event (EmpathyTheme *theme,
71 EmpathyChatView *view,
73 static void theme_boxes_append_timestamp (EmpathyTheme *theme,
74 EmpathyChatView *view,
75 EmpathyMessage *message,
78 static void theme_boxes_append_spacing (EmpathyTheme *theme,
79 EmpathyChatView *view);
83 PROP_HEADER_FOREGROUND,
84 PROP_HEADER_BACKGROUND,
85 PROP_HEADER_LINE_BACKGROUND,
88 PROP_ACTION_FOREGROUND,
89 PROP_HIGHLIGHT_FOREGROUND,
91 PROP_EVENT_FOREGROUND,
92 PROP_INVITE_FOREGROUND,
101 G_DEFINE_TYPE (EmpathyThemeBoxes, empathy_theme_boxes, EMPATHY_TYPE_THEME);
104 empathy_theme_boxes_class_init (EmpathyThemeBoxesClass *class)
106 GObjectClass *object_class;
107 EmpathyThemeClass *theme_class;
109 object_class = G_OBJECT_CLASS (class);
110 theme_class = EMPATHY_THEME_CLASS (class);
112 object_class->finalize = theme_boxes_finalize;
113 object_class->get_property = theme_boxes_get_property;
114 object_class->set_property = theme_boxes_set_property;
116 theme_class->update_view = theme_boxes_update_view;
117 theme_class->append_message = theme_boxes_append_message;
118 theme_class->append_event = theme_boxes_append_event;
119 theme_class->append_timestamp = theme_boxes_append_timestamp;
120 theme_class->append_spacing = theme_boxes_append_spacing;
122 g_object_class_install_property (object_class,
123 PROP_HEADER_FOREGROUND,
124 g_param_spec_string ("header-foreground",
130 g_object_class_install_property (object_class,
131 PROP_HEADER_BACKGROUND,
132 g_param_spec_string ("header-background",
138 g_object_class_install_property (object_class,
139 PROP_HEADER_LINE_BACKGROUND,
140 g_param_spec_string ("header-line-background",
147 g_object_class_install_property (object_class,
148 PROP_TEXT_FOREGROUND,
149 g_param_spec_string ("text-foreground",
155 g_object_class_install_property (object_class,
156 PROP_TEXT_BACKGROUND,
157 g_param_spec_string ("text-background",
163 g_object_class_install_property (object_class,
164 PROP_ACTION_FOREGROUND,
165 g_param_spec_string ("action-foreground",
171 g_object_class_install_property (object_class,
172 PROP_HIGHLIGHT_FOREGROUND,
173 g_param_spec_string ("highlight-foreground",
179 g_object_class_install_property (object_class,
180 PROP_TIME_FOREGROUND,
181 g_param_spec_string ("time-foreground",
187 g_object_class_install_property (object_class,
188 PROP_EVENT_FOREGROUND,
189 g_param_spec_string ("event-foreground",
195 g_object_class_install_property (object_class,
196 PROP_INVITE_FOREGROUND,
197 g_param_spec_string ("invite-foreground",
203 g_object_class_install_property (object_class,
204 PROP_LINK_FOREGROUND,
205 g_param_spec_string ("link-foreground",
211 g_type_class_add_private (object_class, sizeof (EmpathyThemeBoxesPriv));
215 empathy_theme_boxes_init (EmpathyThemeBoxes *theme)
217 EmpathyThemeBoxesPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
218 EMPATHY_TYPE_THEME_BOXES, EmpathyThemeBoxesPriv);
224 theme_boxes_finalize (GObject *object)
226 EmpathyThemeBoxesPriv *priv;
228 priv = GET_PRIV (object);
230 g_free (priv->header_foreground);
231 g_free (priv->header_background);
232 g_free (priv->header_line_background);
233 g_free (priv->text_foreground);
234 g_free (priv->text_background);
235 g_free (priv->action_foreground);
236 g_free (priv->highlight_foreground);
237 g_free (priv->time_foreground);
238 g_free (priv->event_foreground);
239 g_free (priv->invite_foreground);
240 g_free (priv->link_foreground);
242 (G_OBJECT_CLASS (empathy_theme_boxes_parent_class)->finalize) (object);
246 theme_boxes_get_property (GObject *object,
251 EmpathyThemeBoxesPriv *priv;
253 priv = GET_PRIV (object);
256 case PROP_HEADER_FOREGROUND:
257 g_value_set_string (value, priv->header_foreground);
259 case PROP_HEADER_BACKGROUND:
260 g_value_set_string (value, priv->header_background);
262 case PROP_HEADER_LINE_BACKGROUND:
263 g_value_set_string (value, priv->header_line_background);
265 case PROP_TEXT_FOREGROUND:
266 g_value_set_string (value, priv->text_foreground);
268 case PROP_TEXT_BACKGROUND:
269 g_value_set_string (value, priv->text_background);
271 case PROP_ACTION_FOREGROUND:
272 g_value_set_string (value, priv->action_foreground);
274 case PROP_HIGHLIGHT_FOREGROUND:
275 g_value_set_string (value, priv->highlight_foreground);
277 case PROP_TIME_FOREGROUND:
278 g_value_set_string (value, priv->time_foreground);
280 case PROP_EVENT_FOREGROUND:
281 g_value_set_string (value, priv->event_foreground);
283 case PROP_INVITE_FOREGROUND:
284 g_value_set_string (value, priv->invite_foreground);
286 case PROP_LINK_FOREGROUND:
287 g_value_set_string (value, priv->link_foreground);
290 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
295 theme_boxes_set_property (GObject *object,
300 EmpathyThemeBoxesPriv *priv;
302 priv = GET_PRIV (object);
305 case PROP_HEADER_FOREGROUND:
306 g_free (priv->header_foreground);
307 priv->header_foreground = g_value_dup_string (value);
308 g_object_notify (object, "header-foreground");
310 case PROP_HEADER_BACKGROUND:
311 g_free (priv->header_background);
312 priv->header_background = g_value_dup_string (value);
313 g_object_notify (object, "header-background");
315 case PROP_HEADER_LINE_BACKGROUND:
316 g_free (priv->header_line_background);
317 priv->header_line_background = g_value_dup_string (value);
318 g_object_notify (object, "header-line_background");
320 case PROP_TEXT_FOREGROUND:
321 g_free (priv->text_foreground);
322 priv->text_foreground = g_value_dup_string (value);
323 g_object_notify (object, "text-foreground");
325 case PROP_TEXT_BACKGROUND:
326 g_free (priv->text_background);
327 priv->text_background = g_value_dup_string (value);
328 g_object_notify (object, "text-background");
330 case PROP_ACTION_FOREGROUND:
331 g_free (priv->action_foreground);
332 priv->action_foreground = g_value_dup_string (value);
333 g_object_notify (object, "action-foreground");
335 case PROP_HIGHLIGHT_FOREGROUND:
336 g_free (priv->highlight_foreground);
337 priv->highlight_foreground = g_value_dup_string (value);
338 g_object_notify (object, "highlight-foreground");
340 case PROP_TIME_FOREGROUND:
341 g_free (priv->time_foreground);
342 priv->time_foreground = g_value_dup_string (value);
343 g_object_notify (object, "time-foreground");
345 case PROP_EVENT_FOREGROUND:
346 g_free (priv->event_foreground);
347 priv->event_foreground = g_value_dup_string (value);
348 g_object_notify (object, "event-foreground");
350 case PROP_INVITE_FOREGROUND:
351 g_free (priv->invite_foreground);
352 priv->invite_foreground = g_value_dup_string (value);
353 g_object_notify (object, "invite-foreground");
355 case PROP_LINK_FOREGROUND:
356 g_free (priv->link_foreground);
357 priv->link_foreground = g_value_dup_string (value);
358 g_object_notify (object, "link-foreground");
361 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
367 theme_boxes_define_theme_tags (EmpathyTheme *theme, EmpathyChatView *view)
369 EmpathyThemeBoxesPriv *priv;
370 GtkTextBuffer *buffer;
373 priv = GET_PRIV (theme);
375 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
377 empathy_text_buffer_tag_set (buffer, "fancy-spacing",
379 "pixels-above-lines", 8,
382 tag = empathy_text_buffer_tag_set (buffer, "fancy-header",
383 "weight", PANGO_WEIGHT_BOLD,
384 "pixels-above-lines", HEADER_PADDING,
385 "pixels-below-lines", HEADER_PADDING,
387 if (priv->header_foreground) {
389 "foreground", priv->header_foreground,
390 "paragraph-background", priv->header_background,
394 tag = empathy_text_buffer_tag_set (buffer, "fancy-header-line",
397 if (priv->header_line_background) {
399 "paragraph-background", priv->header_line_background,
403 tag = empathy_text_buffer_tag_set (buffer, "fancy-body",
404 "pixels-above-lines", 4,
406 if (priv->text_background) {
408 "paragraph-background", priv->text_background,
412 if (priv->text_foreground) {
414 "foreground", priv->text_foreground,
418 tag = empathy_text_buffer_tag_set (buffer, "fancy-action",
419 "style", PANGO_STYLE_ITALIC,
420 "pixels-above-lines", 4,
423 if (priv->text_background) {
425 "paragraph-background", priv->text_background,
429 if (priv->action_foreground) {
431 "foreground", priv->action_foreground,
435 tag = empathy_text_buffer_tag_set (buffer, "fancy-highlight",
436 "weight", PANGO_WEIGHT_BOLD,
437 "pixels-above-lines", 4,
439 if (priv->text_background) {
441 "paragraph-background", priv->text_background,
446 if (priv->highlight_foreground) {
448 "foreground", priv->highlight_foreground,
452 tag = empathy_text_buffer_tag_set (buffer, "fancy-time",
453 "justification", GTK_JUSTIFY_CENTER,
455 if (priv->time_foreground) {
457 "foreground", priv->time_foreground,
461 tag = empathy_text_buffer_tag_set (buffer, "fancy-event",
462 "justification", GTK_JUSTIFY_LEFT,
464 if (priv->event_foreground) {
466 "foreground", priv->event_foreground,
470 tag = empathy_text_buffer_tag_set (buffer, "invite", NULL);
471 if (priv->invite_foreground) {
473 "foreground", priv->invite_foreground,
477 tag = empathy_text_buffer_tag_set (buffer, "fancy-link",
478 "underline", PANGO_UNDERLINE_SINGLE,
480 if (priv->link_foreground) {
482 "foreground", priv->link_foreground,
488 theme_boxes_update_view (EmpathyTheme *theme, EmpathyChatView *view)
490 EmpathyThemeBoxesPriv *priv;
492 g_return_if_fail (EMPATHY_IS_THEME_BOXES (theme));
493 g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
495 priv = GET_PRIV (theme);
497 theme_boxes_define_theme_tags (theme, view);
499 empathy_chat_view_set_margin (view, MARGIN);
503 table_size_allocate_cb (GtkWidget *view,
504 GtkAllocation *allocation,
509 gtk_widget_get_size_request (box, NULL, &height);
511 width = allocation->width;
514 gtk_text_view_get_right_margin (GTK_TEXT_VIEW (view)) - \
515 gtk_text_view_get_left_margin (GTK_TEXT_VIEW (view));
517 width -= 2 * HEADER_PADDING;
519 gtk_widget_set_size_request (box, width, height);
522 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
523 * pixbuf. Returns a new ref.
526 theme_boxes_pad_to_size (GdkPixbuf *pixbuf,
529 gint extra_padding_right)
531 gint src_width, src_height;
533 gint x_offset, y_offset;
535 src_width = gdk_pixbuf_get_width (pixbuf);
536 src_height = gdk_pixbuf_get_height (pixbuf);
538 x_offset = (width - src_width) / 2;
539 y_offset = (height - src_height) / 2;
541 padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
543 gdk_pixbuf_get_bits_per_sample (pixbuf),
544 width + extra_padding_right,
547 gdk_pixbuf_fill (padded, 0);
549 gdk_pixbuf_copy_area (pixbuf,
550 0, /* source coords */
555 x_offset, /* dest coords */
567 theme_boxes_avatar_cache_data_free (gpointer ptr)
569 AvatarData *data = ptr;
571 g_object_unref (data->pixbuf);
572 g_free (data->token);
573 g_slice_free (AvatarData, data);
577 theme_boxes_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
580 EmpathyAvatar *avatar;
581 GdkPixbuf *tmp_pixbuf;
582 GdkPixbuf *pixbuf = NULL;
584 /* Check if avatar is in cache and if it's up to date */
585 avatar = empathy_contact_get_avatar (contact);
586 data = g_object_get_data (G_OBJECT (contact), "chat-view-avatar-cache");
588 if (avatar && !tp_strdiff (avatar->token, data->token)) {
589 /* We have the avatar in cache */
594 /* Avatar not in cache, create pixbuf */
595 tmp_pixbuf = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
597 pixbuf = theme_boxes_pad_to_size (tmp_pixbuf, 32, 32, 6);
598 g_object_unref (tmp_pixbuf);
604 /* Insert new pixbuf in cache */
605 data = g_slice_new0 (AvatarData);
606 data->token = g_strdup (avatar->token);
607 data->pixbuf = pixbuf;
609 g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
610 data, theme_boxes_avatar_cache_data_free);
616 theme_boxes_maybe_append_header (EmpathyTheme *theme,
617 EmpathyChatView *view,
620 EmpathyThemeBoxesPriv *priv;
621 EmpathyContact *contact;
622 EmpathyContact *last_contact;
623 GdkPixbuf *avatar = NULL;
624 GtkTextBuffer *buffer;
627 GtkWidget *label1, *label2;
628 GtkTextChildAnchor *anchor;
635 gboolean parse_success;
637 priv = GET_PRIV (theme);
639 contact = empathy_message_get_sender (msg);
640 name = empathy_contact_get_name (contact);
641 last_contact = empathy_chat_view_get_last_contact (view);
642 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
644 DEBUG ("Maybe add fancy header");
646 /* Only insert a header if the previously inserted block is not the same
647 * as this one. This catches all the different cases:
649 if (last_contact && empathy_contact_equal (last_contact, contact)) {
653 empathy_theme_append_spacing (theme, view);
655 gtk_text_buffer_get_end_iter (buffer, &iter);
656 gtk_text_buffer_insert_with_tags_by_name (buffer,
663 gtk_text_buffer_get_end_iter (buffer, &iter);
664 anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
666 box = gtk_hbox_new (FALSE, 0);
669 if (empathy_theme_get_show_avatars (theme)) {
670 avatar = theme_boxes_get_avatar_pixbuf_with_cache (contact);
674 image = gtk_image_new_from_pixbuf (avatar);
676 gtk_box_pack_start (GTK_BOX (box), image,
681 g_signal_connect_object (view, "size-allocate",
682 G_CALLBACK (table_size_allocate_cb),
685 str = g_markup_printf_escaped ("<b>%s</b>", name);
687 label1 = g_object_new (GTK_TYPE_LABEL,
693 parse_success = priv->header_foreground &&
694 gdk_color_parse (priv->header_foreground, &color);
697 gtk_widget_modify_fg (label1, GTK_STATE_NORMAL, &color);
702 time = empathy_message_get_timestamp (msg);
704 tmp = empathy_time_to_string_local (time,
705 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
706 str = g_strdup_printf ("<i>%s</i>", tmp);
709 label2 = g_object_new (GTK_TYPE_LABEL,
716 gtk_widget_modify_fg (label2, GTK_STATE_NORMAL, &color);
721 gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.5);
722 gtk_misc_set_alignment (GTK_MISC (label2), 1.0, 0.5);
724 gtk_box_pack_start (GTK_BOX (box), label1, TRUE, TRUE, 0);
725 gtk_box_pack_start (GTK_BOX (box), label2, TRUE, TRUE, 0);
727 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view),
731 gtk_widget_show_all (box);
733 gtk_text_buffer_get_end_iter (buffer, &iter);
735 gtk_text_iter_backward_char (&start);
736 gtk_text_buffer_apply_tag_by_name (buffer,
740 gtk_text_buffer_insert_with_tags_by_name (buffer,
747 gtk_text_buffer_get_end_iter (buffer, &iter);
748 gtk_text_buffer_insert_with_tags_by_name (buffer,
757 theme_boxes_append_message (EmpathyTheme *theme,
758 EmpathyChatView *view,
759 EmpathyMessage *message)
761 EmpathyContact *sender;
763 empathy_theme_maybe_append_date_and_time (theme, view, message);
764 theme_boxes_maybe_append_header (theme, view, message);
766 sender = empathy_message_get_sender (message);
768 if (empathy_message_get_tptype (message) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
771 body = g_strdup_printf (" * %s %s",
772 empathy_contact_get_name (sender),
773 empathy_message_get_body (message));
774 empathy_theme_append_text (theme, view, body,
775 "fancy-action", "fancy-link");
777 empathy_theme_append_text (theme, view,
778 empathy_message_get_body (message),
779 "fancy-body", "fancy-link");
784 theme_boxes_append_event (EmpathyTheme *theme,
785 EmpathyChatView *view,
788 GtkTextBuffer *buffer;
792 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
794 empathy_theme_maybe_append_date_and_time (theme, view, NULL);
796 gtk_text_buffer_get_end_iter (buffer, &iter);
798 msg = g_strdup_printf (" - %s\n", str);
800 gtk_text_buffer_insert_with_tags_by_name (buffer, &iter,
808 theme_boxes_append_timestamp (EmpathyTheme *theme,
809 EmpathyChatView *view,
810 EmpathyMessage *message,
814 GtkTextBuffer *buffer;
824 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
826 date = empathy_message_get_date_and_time (message, ×tamp);
828 str = g_string_new (NULL);
830 if (show_time || show_date) {
831 empathy_theme_append_spacing (theme, view);
833 g_string_append (str, "- ");
839 g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
840 g_string_append (str, buf);
843 g_string_append (str, ", ");
852 tmp = empathy_time_to_string_local (timestamp, EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
853 g_string_append (str, tmp);
857 if (show_time || show_date) {
858 g_string_append (str, " -\n");
860 gtk_text_buffer_get_end_iter (buffer, &iter);
861 gtk_text_buffer_insert_with_tags_by_name (buffer,
867 empathy_chat_view_set_last_timestamp (view, timestamp);
870 g_string_free (str, TRUE);
875 theme_boxes_append_spacing (EmpathyTheme *theme,
876 EmpathyChatView *view)
878 GtkTextBuffer *buffer;
881 g_return_if_fail (EMPATHY_IS_THEME (theme));
882 g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
884 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
886 gtk_text_buffer_get_end_iter (buffer, &iter);
887 gtk_text_buffer_insert_with_tags_by_name (buffer,