1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2008-2009 Collabora Ltd.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library 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 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * Authors: Xavier Claessens <xclaesse@gmail.com>
25 #include <glib/gi18n.h>
27 #include <webkit/webkit.h>
28 #include <telepathy-glib/dbus.h>
29 #include <telepathy-glib/util.h>
31 #include <pango/pango.h>
34 #include <libempathy/empathy-gsettings.h>
35 #include <libempathy/empathy-time.h>
36 #include <libempathy/empathy-utils.h>
38 #include "empathy-theme-adium.h"
39 #include "empathy-smiley-manager.h"
40 #include "empathy-ui-utils.h"
41 #include "empathy-plist.h"
42 #include "empathy-string-parser.h"
43 #include "empathy-images.h"
45 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
46 #include <libempathy/empathy-debug.h>
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
50 #define BORING_DPI_DEFAULT 96
52 /* "Join" consecutive messages with timestamps within five minutes */
53 #define MESSAGE_JOIN_PERIOD 5*60
56 EmpathyAdiumData *data;
57 EmpathySmileyManager *smiley_manager;
58 EmpathyContact *last_contact;
59 gint64 last_timestamp;
60 gboolean last_is_backlog;
62 /* Queue of GValue* containing an EmpathyMessage or string */
64 GtkWidget *inspector_window;
65 GSettings *gsettings_chat;
67 gboolean has_unread_message;
68 gboolean allow_scrolling;
69 } EmpathyThemeAdiumPriv;
71 struct _EmpathyAdiumData {
75 gchar *default_avatar_filename;
76 gchar *default_incoming_avatar_filename;
77 gchar *default_outgoing_avatar_filename;
83 gboolean custom_template;
86 gchar *in_content_html;
88 gchar *in_context_html;
90 gchar *in_nextcontent_html;
91 gsize in_nextcontent_len;
92 gchar *in_nextcontext_html;
93 gsize in_nextcontext_len;
94 gchar *out_content_html;
95 gsize out_content_len;
96 gchar *out_context_html;
97 gsize out_context_len;
98 gchar *out_nextcontent_html;
99 gsize out_nextcontent_len;
100 gchar *out_nextcontext_html;
101 gsize out_nextcontext_len;
106 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
113 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
114 WEBKIT_TYPE_WEB_VIEW,
115 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
116 theme_adium_iface_init));
119 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
121 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
122 WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
123 gboolean enable_webkit_developer_tools;
125 enable_webkit_developer_tools = g_settings_get_boolean (
126 priv->gsettings_chat,
127 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS);
129 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
130 "enable-developer-extras",
131 enable_webkit_developer_tools,
136 theme_adium_notify_enable_webkit_developer_tools_cb (GSettings *gsettings,
140 EmpathyThemeAdium *theme = user_data;
142 theme_adium_update_enable_webkit_developer_tools (theme);
146 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView *view,
147 WebKitWebFrame *web_frame,
148 WebKitNetworkRequest *request,
149 WebKitWebNavigationAction *action,
150 WebKitWebPolicyDecision *decision,
155 /* Only call url_show on clicks */
156 if (webkit_web_navigation_action_get_reason (action) !=
157 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
158 webkit_web_policy_decision_use (decision);
162 uri = webkit_network_request_get_uri (request);
163 empathy_url_show (GTK_WIDGET (view), uri);
165 webkit_web_policy_decision_ignore (decision);
170 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
173 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
175 GtkClipboard *clipboard;
177 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
179 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
180 gtk_clipboard_set_text (clipboard, uri, -1);
182 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
183 gtk_clipboard_set_text (clipboard, uri, -1);
189 theme_adium_open_address_cb (GtkMenuItem *menuitem,
192 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
195 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
197 empathy_url_show (GTK_WIDGET (menuitem), uri);
202 /* Replace each %@ in format with string passed in args */
204 string_with_format (const gchar *format,
205 const gchar *first_string,
212 va_start (args, first_string);
213 result = g_string_sized_new (strlen (format));
214 for (str = first_string; str != NULL; str = va_arg (args, const gchar *)) {
217 next = strstr (format, "%@");
222 g_string_append_len (result, format, next - format);
223 g_string_append (result, str);
226 g_string_append (result, format);
229 return g_string_free (result, FALSE);
233 theme_adium_match_newline (const gchar *text,
235 EmpathyStringReplace replace_func,
236 EmpathyStringParser *sub_parsers,
239 GString *string = user_data;
247 /* Replace \n by <br/> */
248 for (i = 0; i < len && text[i] != '\0'; i++) {
249 if (text[i] == '\n') {
250 empathy_string_parser_substr (text + prev,
251 i - prev, sub_parsers,
253 g_string_append (string, "<br/>");
257 empathy_string_parser_substr (text + prev, i - prev,
258 sub_parsers, user_data);
262 theme_adium_replace_smiley (const gchar *text,
267 EmpathySmileyHit *hit = match_data;
268 GString *string = user_data;
270 /* Replace smiley by a <img/> tag */
271 g_string_append_printf (string,
272 "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
273 hit->path, (int)len, text, (int)len, text);
276 static EmpathyStringParser string_parsers[] = {
277 {empathy_string_match_link, empathy_string_replace_link},
278 {theme_adium_match_newline, NULL},
279 {empathy_string_match_all, empathy_string_replace_escaped},
283 static EmpathyStringParser string_parsers_with_smiley[] = {
284 {empathy_string_match_link, empathy_string_replace_link},
285 {empathy_string_match_smiley, theme_adium_replace_smiley},
286 {theme_adium_match_newline, NULL},
287 {empathy_string_match_all, empathy_string_replace_escaped},
292 theme_adium_parse_body (EmpathyThemeAdium *self,
295 EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
296 EmpathyStringParser *parsers;
299 /* Check if we have to parse smileys */
300 if (g_settings_get_boolean (priv->gsettings_chat,
301 EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
302 parsers = string_parsers_with_smiley;
304 parsers = string_parsers;
306 /* Parse text and construct string with links and smileys replaced
307 * by html tags. Also escape text to make sure html code is
308 * displayed verbatim. */
309 string = g_string_sized_new (strlen (text));
310 empathy_string_parser_substr (text, -1, parsers, string);
312 /* Wrap body in order to make tabs and multiple spaces displayed
313 * properly. See bug #625745. */
314 g_string_prepend (string, "<div style=\"display: inline; "
315 "white-space: pre-wrap\"'>");
316 g_string_append (string, "</div>");
318 return g_string_free (string, FALSE);
322 escape_and_append_len (GString *string, const gchar *str, gint len)
324 while (str != NULL && *str != '\0' && len != 0) {
328 g_string_append (string, "\\\\");
332 g_string_append (string, "\\\"");
335 /* Remove end of lines */
338 g_string_append_c (string, *str);
346 /* If *str starts with match, returns TRUE and move pointer to the end */
348 theme_adium_match (const gchar **str,
353 len = strlen (match);
354 if (strncmp (*str, match, len) == 0) {
362 /* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
364 theme_adium_match_with_format (const gchar **str,
368 const gchar *cur = *str;
371 if (!theme_adium_match (&cur, match)) {
376 end = strstr (cur, "}%");
381 *format = g_strndup (cur , end - cur);
386 /* List of colors used by %senderColor%. Copied from
387 * adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
389 static gchar *colors[] = {
390 "aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
391 "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
392 "darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
393 "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
394 "darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
395 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
396 "dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
397 "green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
398 "lightblue", "lightcoral",
399 "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
400 "lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
401 "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
402 "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
403 "mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
404 "olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
405 "palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
406 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
407 "sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
408 "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
413 theme_adium_append_html (EmpathyThemeAdium *theme,
415 const gchar *html, gsize len,
416 const gchar *message,
417 const gchar *avatar_filename,
419 const gchar *contact_id,
420 const gchar *service_name,
421 const gchar *message_classes,
426 const gchar *cur = NULL;
429 /* Make some search-and-replace in the html code */
430 string = g_string_sized_new (len + strlen (message));
431 g_string_append_printf (string, "%s(\"", func);
432 for (cur = html; *cur != '\0'; cur++) {
433 const gchar *replace = NULL;
434 gchar *dup_replace = NULL;
435 gchar *format = NULL;
437 /* Those are all well known keywords that needs replacement in
438 * html files. Please keep them in the same order than the adium
439 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
440 if (theme_adium_match (&cur, "%userIconPath%")) {
441 replace = avatar_filename;
442 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
443 replace = contact_id;
444 } else if (theme_adium_match (&cur, "%sender%")) {
446 } else if (theme_adium_match (&cur, "%senderColor%")) {
447 /* A color derived from the user's name.
448 * FIXME: If a colon separated list of HTML colors is at
449 * Incoming/SenderColors.txt it will be used instead of
450 * the default colors.
452 if (contact_id != NULL) {
453 guint hash = g_str_hash (contact_id);
454 replace = colors[hash % G_N_ELEMENTS (colors)];
456 } else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
457 /* FIXME: The path to the status icon of the sender
458 * (available, away, etc...)
460 } else if (theme_adium_match (&cur, "%messageDirection%")) {
461 /* FIXME: The text direction of the message
462 * (either rtl or ltr)
464 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
465 /* FIXME: The serverside (remotely set) name of the
466 * sender, such as an MSN display name.
468 * We don't have access to that yet so we use
469 * local alias instead.
472 } else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
473 /* FIXME: This keyword is used to represent the
474 * highlight background color. "X" is the opacity of the
475 * background, ranges from 0 to 1 and can be any decimal
478 } else if (theme_adium_match (&cur, "%message%")) {
480 } else if (theme_adium_match (&cur, "%time%") ||
481 theme_adium_match_with_format (&cur, "%time{", &format)) {
482 /* FIXME: format is not exactly strftime.
483 * See NSDateFormatter spec:
484 * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
487 dup_replace = empathy_time_to_string_local (timestamp,
488 format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
490 dup_replace = empathy_time_to_string_local (timestamp,
491 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
493 replace = dup_replace;
494 } else if (theme_adium_match (&cur, "%shortTime%")) {
495 dup_replace = empathy_time_to_string_local (timestamp,
496 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
497 replace = dup_replace;
498 } else if (theme_adium_match (&cur, "%service%")) {
499 replace = service_name;
500 } else if (theme_adium_match (&cur, "%variant%")) {
501 /* FIXME: The name of the active message style variant,
502 * with all spaces replaced with an underscore.
503 * A variant named "Alternating Messages - Blue Red"
504 * will become "Alternating_Messages_-_Blue_Red".
506 } else if (theme_adium_match (&cur, "%userIcons%")) {
507 /* FIXME: mus t be "hideIcons" if use preference is set
509 replace = "showIcons";
510 } else if (theme_adium_match (&cur, "%messageClasses%")) {
511 replace = message_classes;
512 } else if (theme_adium_match (&cur, "%status%")) {
513 /* FIXME: A description of the status event. This is
514 * neither in the user's local language nor expected to
515 * be displayed; it may be useful to use a different div
516 * class to present different types of status messages.
517 * The following is a list of some of the more important
518 * status messages; your message style should be able to
519 * handle being shown a status message not in this list,
520 * as even at present the list is incomplete and is
521 * certain to become out of date in the future:
530 * contact_joined (group chats)
534 * encryption (all OTR messages use this status)
535 * purple (all IRC topic and join/part messages use this status)
536 * fileTransferStarted
537 * fileTransferCompleted
540 escape_and_append_len (string, cur, 1);
544 /* Here we have a replacement to make */
545 escape_and_append_len (string, replace, -1);
547 g_free (dup_replace);
550 g_string_append (string, "\")");
552 script = g_string_free (string, FALSE);
553 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
558 theme_adium_append_event_escaped (EmpathyChatView *view,
559 const gchar *escaped)
561 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
562 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
566 html = priv->data->content_html;
567 len = priv->data->content_len;
569 /* Fallback to legacy status_html */
571 html = priv->data->status_html;
572 len = priv->data->status_len;
576 theme_adium_append_html (theme, "appendMessage",
577 html, len, escaped, NULL, NULL, NULL,
579 empathy_time_get_current (), FALSE);
581 DEBUG ("Couldn't find HTML file for this event");
584 /* There is no last contact */
585 if (priv->last_contact) {
586 g_object_unref (priv->last_contact);
587 priv->last_contact = NULL;
592 theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
594 WebKitDOMDocument *dom;
595 WebKitDOMNodeList *nodes;
597 GError *error = NULL;
599 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
604 /* Get all nodes with focus class */
605 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
607 DEBUG ("Error getting focus nodes: %s",
608 error ? error->message : "No error");
609 g_clear_error (&error);
613 /* Remove focus and firstFocus class */
614 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
615 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
616 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
618 gchar **classes, **iter;
619 GString *new_class_name;
620 gboolean first = TRUE;
622 if (element == NULL) {
626 class_name = webkit_dom_html_element_get_class_name (element);
627 classes = g_strsplit (class_name, " ", -1);
628 new_class_name = g_string_sized_new (strlen (class_name));
629 for (iter = classes; *iter != NULL; iter++) {
630 if (tp_strdiff (*iter, "focus") &&
631 tp_strdiff (*iter, "firstFocus")) {
633 g_string_append_c (new_class_name, ' ');
635 g_string_append (new_class_name, *iter);
640 webkit_dom_html_element_set_class_name (element, new_class_name->str);
643 g_strfreev (classes);
644 g_string_free (new_class_name, TRUE);
649 theme_adium_append_message (EmpathyChatView *view,
652 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
653 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
654 EmpathyContact *sender;
659 const gchar *contact_id;
660 EmpathyAvatar *avatar;
661 const gchar *avatar_filename = NULL;
666 const gchar *service_name;
667 GString *message_classes = NULL;
669 gboolean consecutive;
672 if (priv->pages_loading != 0) {
673 GValue *value = tp_g_value_slice_new (EMPATHY_TYPE_MESSAGE);
674 g_value_set_object (value, msg);
675 g_queue_push_tail (&priv->message_queue, value);
679 /* Get information */
680 sender = empathy_message_get_sender (msg);
681 account = empathy_contact_get_account (sender);
682 service_name = empathy_protocol_name_to_display_name
683 (tp_account_get_protocol (account));
684 if (service_name == NULL)
685 service_name = tp_account_get_protocol (account);
686 timestamp = empathy_message_get_timestamp (msg);
687 body = empathy_message_get_body (msg);
688 body_escaped = theme_adium_parse_body (theme, body);
689 name = empathy_contact_get_alias (sender);
690 contact_id = empathy_contact_get_id (sender);
691 action = (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
693 /* If this is a /me probably */
697 if (priv->data->version >= 4 || !priv->data->custom_template) {
698 str = g_strdup_printf ("<span class='actionMessageUserName'>%s</span>"
699 "<span class='actionMessageBody'>%s</span>",
702 str = g_strdup_printf ("*%s*", body_escaped);
704 g_free (body_escaped);
708 /* Get the avatar filename, or a fallback */
709 avatar = empathy_contact_get_avatar (sender);
711 avatar_filename = avatar->filename;
713 if (!avatar_filename) {
714 if (empathy_contact_is_user (sender)) {
715 avatar_filename = priv->data->default_outgoing_avatar_filename;
717 avatar_filename = priv->data->default_incoming_avatar_filename;
719 if (!avatar_filename) {
720 if (!priv->data->default_avatar_filename) {
721 priv->data->default_avatar_filename =
722 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
723 GTK_ICON_SIZE_DIALOG);
725 avatar_filename = priv->data->default_avatar_filename;
729 /* We want to join this message with the last one if
730 * - senders are the same contact,
731 * - last message was recieved recently,
732 * - last message and this message both are/aren't backlog, and
733 * - DisableCombineConsecutive is not set in theme's settings */
734 is_backlog = empathy_message_is_backlog (msg);
735 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
736 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
737 (is_backlog == priv->last_is_backlog) &&
738 !tp_asv_get_boolean (priv->data->info,
739 "DisableCombineConsecutive", NULL);
741 /* Define message classes */
742 message_classes = g_string_new ("message");
743 if (!priv->has_focus && !is_backlog) {
744 if (!priv->has_unread_message) {
745 /* This is the first message we receive since we lost
746 * focus; remove previous unread marks. */
747 theme_adium_remove_focus_marks (theme);
749 g_string_append (message_classes, " firstFocus");
750 priv->has_unread_message = TRUE;
752 g_string_append (message_classes, " focus");
755 g_string_append (message_classes, " history");
758 g_string_append (message_classes, " consecutive");
760 if (empathy_contact_is_user (sender)) {
761 g_string_append (message_classes, " outgoing");
763 g_string_append (message_classes, " incoming");
765 if (empathy_message_should_highlight (msg)) {
766 g_string_append (message_classes, " mention");
768 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
769 g_string_append (message_classes, " autoreply");
772 g_string_append (message_classes, " action");
774 /* FIXME: other classes:
775 * status - the message is a status change
776 * event - the message is a notification of something happening
777 * (for example, encryption being turned on)
778 * %status% - See %status% in theme_adium_append_html ()
781 /* Define javascript function to use */
783 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
785 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
788 html = priv->data->content_html;
789 len = priv->data->content_len;
791 /* Fallback to legacy Outgoing */
792 if (html == NULL && empathy_contact_is_user (sender)) {
795 html = priv->data->out_nextcontext_html;
796 len = priv->data->out_nextcontext_len;
799 /* Not backlog, or fallback if NextContext.html
802 html = priv->data->out_nextcontent_html;
803 len = priv->data->out_nextcontent_len;
807 /* Not consecutive, or fallback if NextContext.html and/or
808 * NextContent.html are missing */
811 html = priv->data->out_context_html;
812 len = priv->data->out_context_len;
816 html = priv->data->out_content_html;
817 len = priv->data->out_content_len;
822 /* Incoming, or fallback if outgoing files are missing */
826 html = priv->data->in_nextcontext_html;
827 len = priv->data->in_nextcontext_len;
830 /* Note backlog, or fallback if NextContext.html
833 html = priv->data->in_nextcontent_html;
834 len = priv->data->in_nextcontent_len;
838 /* Not consecutive, or fallback if NextContext.html and/or
839 * NextContent.html are missing */
842 html = priv->data->in_context_html;
843 len = priv->data->in_context_len;
847 html = priv->data->in_content_html;
848 len = priv->data->in_content_len;
854 theme_adium_append_html (theme, func, html, len, body_escaped,
855 avatar_filename, name, contact_id,
856 service_name, message_classes->str,
857 timestamp, is_backlog);
859 DEBUG ("Couldn't find HTML file for this message");
862 /* Keep the sender of the last displayed message */
863 if (priv->last_contact) {
864 g_object_unref (priv->last_contact);
866 priv->last_contact = g_object_ref (sender);
867 priv->last_timestamp = timestamp;
868 priv->last_is_backlog = is_backlog;
870 g_free (body_escaped);
871 g_string_free (message_classes, TRUE);
875 theme_adium_append_event (EmpathyChatView *view,
878 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
881 if (priv->pages_loading != 0) {
882 g_queue_push_tail (&priv->message_queue,
883 tp_g_value_slice_new_string (str));
887 str_escaped = g_markup_escape_text (str, -1);
888 theme_adium_append_event_escaped (view, str_escaped);
889 g_free (str_escaped);
893 theme_adium_scroll (EmpathyChatView *view,
894 gboolean allow_scrolling)
896 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
898 priv->allow_scrolling = allow_scrolling;
899 if (allow_scrolling) {
900 empathy_chat_view_scroll_down (view);
905 theme_adium_scroll_down (EmpathyChatView *view)
907 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
911 theme_adium_get_has_selection (EmpathyChatView *view)
913 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
917 theme_adium_clear (EmpathyChatView *view)
919 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
922 priv->pages_loading++;
923 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
924 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
925 priv->data->template_html,
927 g_free (basedir_uri);
929 /* Clear last contact to avoid trying to add a 'joined'
930 * message when we don't have an insertion point. */
931 if (priv->last_contact) {
932 g_object_unref (priv->last_contact);
933 priv->last_contact = NULL;
938 theme_adium_find_previous (EmpathyChatView *view,
939 const gchar *search_criteria,
943 /* FIXME: Doesn't respect new_search */
944 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
945 search_criteria, match_case,
950 theme_adium_find_next (EmpathyChatView *view,
951 const gchar *search_criteria,
955 /* FIXME: Doesn't respect new_search */
956 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
957 search_criteria, match_case,
962 theme_adium_find_abilities (EmpathyChatView *view,
963 const gchar *search_criteria,
965 gboolean *can_do_previous,
966 gboolean *can_do_next)
968 /* FIXME: Does webkit provide an API for that? We have wrap=true in
969 * find_next and find_previous to work around this problem. */
971 *can_do_previous = TRUE;
977 theme_adium_highlight (EmpathyChatView *view,
981 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
982 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
983 text, match_case, 0);
984 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
989 theme_adium_copy_clipboard (EmpathyChatView *view)
991 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
995 theme_adium_focus_toggled (EmpathyChatView *view,
998 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1000 priv->has_focus = has_focus;
1001 if (priv->has_focus) {
1002 priv->has_unread_message = FALSE;
1007 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
1009 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
1011 g_object_unref (hit_test_result);
1015 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
1017 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
1018 WebKitHitTestResult *hit_test_result;
1019 WebKitHitTestResultContext context;
1023 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
1024 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
1027 menu = empathy_context_menu_new (GTK_WIDGET (view));
1029 /* Select all item */
1030 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
1031 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1033 g_signal_connect_swapped (item, "activate",
1034 G_CALLBACK (webkit_web_view_select_all),
1037 /* Copy menu item */
1038 if (webkit_web_view_can_copy_clipboard (view)) {
1039 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
1040 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1042 g_signal_connect_swapped (item, "activate",
1043 G_CALLBACK (webkit_web_view_copy_clipboard),
1047 /* Clear menu item */
1048 item = gtk_separator_menu_item_new ();
1049 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1051 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
1052 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1054 g_signal_connect_swapped (item, "activate",
1055 G_CALLBACK (empathy_chat_view_clear),
1058 /* We will only add the following menu items if we are
1059 * right-clicking a link */
1060 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1062 item = gtk_separator_menu_item_new ();
1063 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1065 /* Copy Link Address menu item */
1066 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1067 g_signal_connect (item, "activate",
1068 G_CALLBACK (theme_adium_copy_address_cb),
1070 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1072 /* Open Link menu item */
1073 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1074 g_signal_connect (item, "activate",
1075 G_CALLBACK (theme_adium_open_address_cb),
1077 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1080 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1081 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1084 /* Display the menu */
1085 gtk_widget_show_all (menu);
1086 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1087 event->button, event->time);
1091 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1093 if (event->button == 3) {
1094 gboolean developer_tools_enabled;
1096 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1097 "enable-developer-extras", &developer_tools_enabled, NULL);
1099 /* We currently have no way to add an inspector menu
1100 * item ourselves, so we disable our customized menu
1101 * if the developer extras are enabled. */
1102 if (!developer_tools_enabled) {
1103 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1108 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1112 theme_adium_iface_init (EmpathyChatViewIface *iface)
1114 iface->append_message = theme_adium_append_message;
1115 iface->append_event = theme_adium_append_event;
1116 iface->scroll = theme_adium_scroll;
1117 iface->scroll_down = theme_adium_scroll_down;
1118 iface->get_has_selection = theme_adium_get_has_selection;
1119 iface->clear = theme_adium_clear;
1120 iface->find_previous = theme_adium_find_previous;
1121 iface->find_next = theme_adium_find_next;
1122 iface->find_abilities = theme_adium_find_abilities;
1123 iface->highlight = theme_adium_highlight;
1124 iface->copy_clipboard = theme_adium_copy_clipboard;
1125 iface->focus_toggled = theme_adium_focus_toggled;
1129 theme_adium_load_finished_cb (WebKitWebView *view,
1130 WebKitWebFrame *frame,
1133 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1134 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1137 DEBUG ("Page loaded");
1138 priv->pages_loading--;
1140 if (priv->pages_loading != 0)
1143 /* Display queued messages */
1144 for (l = priv->message_queue.head; l != NULL; l = l->next) {
1145 GValue *value = l->data;
1147 if (G_VALUE_HOLDS_OBJECT (value)) {
1148 theme_adium_append_message (chat_view,
1149 g_value_get_object (value));
1151 theme_adium_append_event (chat_view,
1152 g_value_get_string (value));
1155 tp_g_value_slice_free (value);
1157 g_queue_clear (&priv->message_queue);
1161 theme_adium_finalize (GObject *object)
1163 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1165 empathy_adium_data_unref (priv->data);
1166 g_object_unref (priv->gsettings_chat);
1168 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1172 theme_adium_dispose (GObject *object)
1174 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1176 if (priv->smiley_manager) {
1177 g_object_unref (priv->smiley_manager);
1178 priv->smiley_manager = NULL;
1181 if (priv->last_contact) {
1182 g_object_unref (priv->last_contact);
1183 priv->last_contact = NULL;
1186 if (priv->inspector_window) {
1187 gtk_widget_destroy (priv->inspector_window);
1188 priv->inspector_window = NULL;
1191 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1195 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1196 EmpathyThemeAdium *theme)
1198 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1200 if (priv->inspector_window) {
1201 gtk_widget_show_all (priv->inspector_window);
1208 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1209 EmpathyThemeAdium *theme)
1211 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1213 if (priv->inspector_window) {
1214 gtk_widget_hide (priv->inspector_window);
1220 static WebKitWebView *
1221 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1222 WebKitWebView *web_view,
1223 EmpathyThemeAdium *theme)
1225 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1226 GtkWidget *scrolled_window;
1227 GtkWidget *inspector_web_view;
1229 if (!priv->inspector_window) {
1230 /* Create main window */
1231 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1232 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1234 g_signal_connect (priv->inspector_window, "delete-event",
1235 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1237 /* Pack a scrolled window */
1238 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1239 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1240 GTK_POLICY_AUTOMATIC,
1241 GTK_POLICY_AUTOMATIC);
1242 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1244 gtk_widget_show (scrolled_window);
1246 /* Pack a webview in the scrolled window. That webview will be
1247 * used to render the inspector tool. */
1248 inspector_web_view = webkit_web_view_new ();
1249 gtk_container_add (GTK_CONTAINER (scrolled_window),
1250 inspector_web_view);
1251 gtk_widget_show (scrolled_window);
1253 return WEBKIT_WEB_VIEW (inspector_web_view);
1259 static PangoFontDescription *
1260 theme_adium_get_default_font (void)
1262 GSettings *gsettings;
1263 PangoFontDescription *pango_fd;
1266 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1268 font_family = g_settings_get_string (gsettings,
1269 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1271 if (font_family == NULL)
1274 pango_fd = pango_font_description_from_string (font_family);
1275 g_free (font_family);
1276 g_object_unref (gsettings);
1281 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1285 g_object_set (w_settings, "default-font-family", name, NULL);
1286 g_object_set (w_settings, "default-font-size", size, NULL);
1290 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1292 PangoFontDescription *default_font_desc;
1293 GdkScreen *current_screen;
1295 gint pango_font_size = 0;
1297 default_font_desc = theme_adium_get_default_font ();
1298 if (default_font_desc == NULL)
1300 pango_font_size = pango_font_description_get_size (default_font_desc)
1302 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1303 current_screen = gdk_screen_get_default ();
1304 if (current_screen != NULL) {
1305 dpi = gdk_screen_get_resolution (current_screen);
1307 dpi = BORING_DPI_DEFAULT;
1309 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1311 theme_adium_set_webkit_font (w_settings,
1312 pango_font_description_get_family (default_font_desc),
1314 pango_font_description_free (default_font_desc);
1318 theme_adium_constructed (GObject *object)
1320 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1322 const gchar *font_family = NULL;
1324 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1325 WebKitWebSettings *webkit_settings;
1326 WebKitWebInspector *webkit_inspector;
1328 /* Set default settings */
1329 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1330 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1331 webkit_settings = webkit_web_view_get_settings (webkit_view);
1333 if (font_family && font_size) {
1334 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1336 theme_adium_set_default_font (webkit_settings);
1339 /* Setup webkit inspector */
1340 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1341 g_signal_connect (webkit_inspector, "inspect-web-view",
1342 G_CALLBACK (theme_adium_inspect_web_view_cb),
1344 g_signal_connect (webkit_inspector, "show-window",
1345 G_CALLBACK (theme_adium_inspector_show_window_cb),
1347 g_signal_connect (webkit_inspector, "close-window",
1348 G_CALLBACK (theme_adium_inspector_close_window_cb),
1352 priv->pages_loading = 1;
1354 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1355 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1356 priv->data->template_html,
1358 g_free (basedir_uri);
1362 theme_adium_get_property (GObject *object,
1367 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1370 case PROP_ADIUM_DATA:
1371 g_value_set_boxed (value, priv->data);
1374 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1380 theme_adium_set_property (GObject *object,
1382 const GValue *value,
1385 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1388 case PROP_ADIUM_DATA:
1389 g_assert (priv->data == NULL);
1390 priv->data = g_value_dup_boxed (value);
1393 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1399 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1401 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1402 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1404 object_class->finalize = theme_adium_finalize;
1405 object_class->dispose = theme_adium_dispose;
1406 object_class->constructed = theme_adium_constructed;
1407 object_class->get_property = theme_adium_get_property;
1408 object_class->set_property = theme_adium_set_property;
1410 widget_class->button_press_event = theme_adium_button_press_event;
1412 g_object_class_install_property (object_class,
1414 g_param_spec_boxed ("adium-data",
1416 "Data for the adium theme",
1417 EMPATHY_TYPE_ADIUM_DATA,
1418 G_PARAM_CONSTRUCT_ONLY |
1420 G_PARAM_STATIC_STRINGS));
1422 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1426 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1428 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1429 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1433 g_queue_init (&priv->message_queue);
1434 priv->allow_scrolling = TRUE;
1435 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1437 g_signal_connect (theme, "load-finished",
1438 G_CALLBACK (theme_adium_load_finished_cb),
1440 g_signal_connect (theme, "navigation-policy-decision-requested",
1441 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1444 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1445 g_signal_connect (priv->gsettings_chat,
1446 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1447 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1450 theme_adium_update_enable_webkit_developer_tools (theme);
1454 empathy_theme_adium_new (EmpathyAdiumData *data)
1456 g_return_val_if_fail (data != NULL, NULL);
1458 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1464 empathy_adium_path_is_valid (const gchar *path)
1469 /* The theme is not valid if there is no Info.plist */
1470 file = g_build_filename (path, "Contents", "Info.plist",
1472 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1478 /* We ship a default Template.html as fallback if there is any problem
1479 * with the one inside the theme. The only other required file is
1481 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1483 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1489 /* Legacy themes have Incoming/Content.html (outgoing fallback to use
1491 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1492 "Content.html", NULL);
1493 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1500 empathy_adium_info_new (const gchar *path)
1504 GHashTable *info = NULL;
1506 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1508 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1509 value = empathy_plist_parse_from_file (file);
1515 info = g_value_dup_boxed (value);
1516 tp_g_value_slice_free (value);
1518 /* Insert the theme's path into the hash table,
1519 * keys have to be dupped */
1520 tp_asv_set_string (info, g_strdup ("path"), path);
1526 adium_info_get_version (GHashTable *info)
1528 return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
1531 static const gchar *
1532 adium_info_get_no_variant_name (GHashTable *info)
1534 const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
1535 return name ? name : _("Normal");
1538 static const gchar *
1539 adium_info_get_default_or_first_variant (GHashTable *info)
1542 GPtrArray *variants;
1544 name = empathy_adium_info_get_default_variant (info);
1549 variants = empathy_adium_info_get_available_variants (info);
1550 g_assert (variants->len > 0);
1551 return g_ptr_array_index (variants, 0);
1555 adium_info_dup_path_for_variant (GHashTable *info,
1556 const gchar *variant)
1558 guint version = adium_info_get_version (info);
1559 const gchar *no_variant = adium_info_get_no_variant_name (info);
1561 if (version <= 2 && !tp_strdiff (variant, no_variant)) {
1562 return g_strdup ("main.css");
1565 return g_strdup_printf ("Variants/%s.css", variant);
1570 empathy_adium_info_get_default_variant (GHashTable *info)
1572 if (adium_info_get_version (info) <= 2) {
1573 return adium_info_get_no_variant_name (info);
1576 return tp_asv_get_string (info, "DefaultVariant");
1580 empathy_adium_info_get_available_variants (GHashTable *info)
1582 GPtrArray *variants;
1587 variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
1588 if (variants != NULL) {
1592 variants = g_ptr_array_new_with_free_func (g_free);
1593 tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
1594 G_TYPE_PTR_ARRAY, variants);
1596 path = tp_asv_get_string (info, "path");
1597 dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
1598 dir = g_dir_open (dirpath, 0, NULL);
1602 for (name = g_dir_read_name (dir);
1604 name = g_dir_read_name (dir)) {
1605 gchar *display_name;
1607 if (!g_str_has_suffix (name, ".css")) {
1611 display_name = g_strdup (name);
1612 strstr (display_name, ".css")[0] = '\0';
1613 g_ptr_array_add (variants, display_name);
1619 if (adium_info_get_version (info) <= 2) {
1620 g_ptr_array_add (variants,
1621 g_strdup (adium_info_get_no_variant_name (info)));
1628 empathy_adium_data_get_type (void)
1630 static GType type_id = 0;
1634 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1635 (GBoxedCopyFunc) empathy_adium_data_ref,
1636 (GBoxedFreeFunc) empathy_adium_data_unref);
1643 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1645 EmpathyAdiumData *data;
1647 gchar *template_html = NULL;
1648 gchar *footer_html = NULL;
1649 gchar *variant_path;
1651 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1653 data = g_slice_new0 (EmpathyAdiumData);
1654 data->ref_count = 1;
1655 data->path = g_strdup (path);
1656 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1657 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1658 data->info = g_hash_table_ref (info);
1659 data->version = adium_info_get_version (info);
1661 DEBUG ("Loading theme at %s", path);
1663 /* Load html files */
1664 file = g_build_filename (data->basedir, "Content.html", NULL);
1665 g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
1668 /* Fallback to legacy html files */
1669 if (data->content_html == NULL) {
1670 DEBUG (" fallback to legacy theme");
1672 file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1673 g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1676 file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1677 g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1680 file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1681 g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1684 file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1685 g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1688 file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1689 g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1692 file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1693 g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1696 file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1697 g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1700 file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1701 g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1704 file = g_build_filename (data->basedir, "Status.html", NULL);
1705 g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1709 file = g_build_filename (data->basedir, "Footer.html", NULL);
1710 g_file_get_contents (file, &footer_html, NULL, NULL);
1713 file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1714 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1715 data->default_incoming_avatar_filename = file;
1720 file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1721 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1722 data->default_outgoing_avatar_filename = file;
1727 file = g_build_filename (data->basedir, "Template.html", NULL);
1728 if (g_file_get_contents (file, &template_html, NULL, NULL)) {
1729 data->custom_template = TRUE;
1733 /* If there were no custom template, fallack to our own */
1734 if (template_html == NULL) {
1735 data->custom_template = FALSE;
1737 file = empathy_file_lookup ("Template.html", "data");
1738 g_file_get_contents (file, &template_html, NULL, NULL);
1742 variant_path = adium_info_dup_path_for_variant (info,
1743 adium_info_get_default_or_first_variant (info));
1745 /* Old custom templates had only 4 parameters.
1746 * New templates have 5 parameters */
1747 if (data->version <= 2 && data->custom_template) {
1748 data->template_html = string_with_format (template_html,
1751 "", /* The header */
1752 footer_html ? footer_html : "",
1755 data->template_html = string_with_format (template_html,
1757 data->version <= 2 ? "" : "@import url( \"main.css\" );",
1759 "", /* The header */
1760 footer_html ? footer_html : "",
1764 g_free (variant_path);
1765 g_free (footer_html);
1766 g_free (template_html);
1772 empathy_adium_data_new (const gchar *path)
1774 EmpathyAdiumData *data;
1777 info = empathy_adium_info_new (path);
1778 data = empathy_adium_data_new_with_info (path, info);
1779 g_hash_table_unref (info);
1785 empathy_adium_data_ref (EmpathyAdiumData *data)
1787 g_return_val_if_fail (data != NULL, NULL);
1789 g_atomic_int_inc (&data->ref_count);
1795 empathy_adium_data_unref (EmpathyAdiumData *data)
1797 g_return_if_fail (data != NULL);
1799 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1800 g_free (data->path);
1801 g_free (data->basedir);
1802 g_free (data->default_avatar_filename);
1803 g_free (data->default_incoming_avatar_filename);
1804 g_free (data->default_outgoing_avatar_filename);
1805 g_free (data->template_html);
1806 g_free (data->content_html);
1807 g_hash_table_unref (data->info);
1809 g_free (data->in_content_html);
1810 g_free (data->in_nextcontent_html);
1811 g_free (data->in_context_html);
1812 g_free (data->in_nextcontext_html);
1813 g_free (data->out_content_html);
1814 g_free (data->out_nextcontent_html);
1815 g_free (data->out_context_html);
1816 g_free (data->out_nextcontext_html);
1817 g_free (data->status_html);
1819 g_slice_free (EmpathyAdiumData, data);
1824 empathy_adium_data_get_info (EmpathyAdiumData *data)
1826 g_return_val_if_fail (data != NULL, NULL);
1832 empathy_adium_data_get_path (EmpathyAdiumData *data)
1834 g_return_val_if_fail (data != NULL, NULL);