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-lib.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;
80 gboolean custom_template;
83 const gchar *template_html;
84 const gchar *content_html;
85 const gchar *in_content_html;
86 const gchar *in_context_html;
87 const gchar *in_nextcontent_html;
88 const gchar *in_nextcontext_html;
89 const gchar *out_content_html;
90 const gchar *out_context_html;
91 const gchar *out_nextcontent_html;
92 const gchar *out_nextcontext_html;
93 const gchar *status_html;
95 /* Above html strings are pointers to strings stored in this array.
96 * We do this because of fallbacks, some htmls could be pointing the
98 GPtrArray *strings_to_free;
101 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
108 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
109 WEBKIT_TYPE_WEB_VIEW,
110 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
111 theme_adium_iface_init));
114 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
116 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
117 WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
118 gboolean enable_webkit_developer_tools;
120 enable_webkit_developer_tools = g_settings_get_boolean (
121 priv->gsettings_chat,
122 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS);
124 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
125 "enable-developer-extras",
126 enable_webkit_developer_tools,
131 theme_adium_notify_enable_webkit_developer_tools_cb (GSettings *gsettings,
135 EmpathyThemeAdium *theme = user_data;
137 theme_adium_update_enable_webkit_developer_tools (theme);
141 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView *view,
142 WebKitWebFrame *web_frame,
143 WebKitNetworkRequest *request,
144 WebKitWebNavigationAction *action,
145 WebKitWebPolicyDecision *decision,
150 /* Only call url_show on clicks */
151 if (webkit_web_navigation_action_get_reason (action) !=
152 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
153 webkit_web_policy_decision_use (decision);
157 uri = webkit_network_request_get_uri (request);
158 empathy_url_show (GTK_WIDGET (view), uri);
160 webkit_web_policy_decision_ignore (decision);
165 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
168 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
170 GtkClipboard *clipboard;
172 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
174 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
175 gtk_clipboard_set_text (clipboard, uri, -1);
177 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
178 gtk_clipboard_set_text (clipboard, uri, -1);
184 theme_adium_open_address_cb (GtkMenuItem *menuitem,
187 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
190 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
192 empathy_url_show (GTK_WIDGET (menuitem), uri);
197 /* Replace each %@ in format with string passed in args */
199 string_with_format (const gchar *format,
200 const gchar *first_string,
207 va_start (args, first_string);
208 result = g_string_sized_new (strlen (format));
209 for (str = first_string; str != NULL; str = va_arg (args, const gchar *)) {
212 next = strstr (format, "%@");
217 g_string_append_len (result, format, next - format);
218 g_string_append (result, str);
221 g_string_append (result, format);
224 return g_string_free (result, FALSE);
228 theme_adium_match_newline (const gchar *text,
230 EmpathyStringReplace replace_func,
231 EmpathyStringParser *sub_parsers,
234 GString *string = user_data;
242 /* Replace \n by <br/> */
243 for (i = 0; i < len && text[i] != '\0'; i++) {
244 if (text[i] == '\n') {
245 empathy_string_parser_substr (text + prev,
246 i - prev, sub_parsers,
248 g_string_append (string, "<br/>");
252 empathy_string_parser_substr (text + prev, i - prev,
253 sub_parsers, user_data);
257 theme_adium_replace_smiley (const gchar *text,
262 EmpathySmileyHit *hit = match_data;
263 GString *string = user_data;
265 /* Replace smiley by a <img/> tag */
266 g_string_append_printf (string,
267 "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
268 hit->path, (int)len, text, (int)len, text);
271 static EmpathyStringParser string_parsers[] = {
272 {empathy_string_match_link, empathy_string_replace_link},
273 {theme_adium_match_newline, NULL},
274 {empathy_string_match_all, empathy_string_replace_escaped},
278 static EmpathyStringParser string_parsers_with_smiley[] = {
279 {empathy_string_match_link, empathy_string_replace_link},
280 {empathy_string_match_smiley, theme_adium_replace_smiley},
281 {theme_adium_match_newline, NULL},
282 {empathy_string_match_all, empathy_string_replace_escaped},
287 theme_adium_parse_body (EmpathyThemeAdium *self,
290 EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
291 EmpathyStringParser *parsers;
294 /* Check if we have to parse smileys */
295 if (g_settings_get_boolean (priv->gsettings_chat,
296 EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
297 parsers = string_parsers_with_smiley;
299 parsers = string_parsers;
301 /* Parse text and construct string with links and smileys replaced
302 * by html tags. Also escape text to make sure html code is
303 * displayed verbatim. */
304 string = g_string_sized_new (strlen (text));
305 empathy_string_parser_substr (text, -1, parsers, string);
307 /* Wrap body in order to make tabs and multiple spaces displayed
308 * properly. See bug #625745. */
309 g_string_prepend (string, "<div style=\"display: inline; "
310 "white-space: pre-wrap\"'>");
311 g_string_append (string, "</div>");
313 return g_string_free (string, FALSE);
317 escape_and_append_len (GString *string, const gchar *str, gint len)
319 while (str != NULL && *str != '\0' && len != 0) {
323 g_string_append (string, "\\\\");
327 g_string_append (string, "\\\"");
330 /* Remove end of lines */
333 g_string_append_c (string, *str);
341 /* If *str starts with match, returns TRUE and move pointer to the end */
343 theme_adium_match (const gchar **str,
348 len = strlen (match);
349 if (strncmp (*str, match, len) == 0) {
357 /* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
359 theme_adium_match_with_format (const gchar **str,
363 const gchar *cur = *str;
366 if (!theme_adium_match (&cur, match)) {
371 end = strstr (cur, "}%");
376 *format = g_strndup (cur , end - cur);
381 /* List of colors used by %senderColor%. Copied from
382 * adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
384 static gchar *colors[] = {
385 "aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
386 "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
387 "darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
388 "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
389 "darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
390 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
391 "dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
392 "green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
393 "lightblue", "lightcoral",
394 "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
395 "lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
396 "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
397 "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
398 "mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
399 "olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
400 "palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
401 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
402 "sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
403 "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
408 theme_adium_append_html (EmpathyThemeAdium *theme,
411 const gchar *message,
412 const gchar *avatar_filename,
414 const gchar *contact_id,
415 const gchar *service_name,
416 const gchar *message_classes,
421 const gchar *cur = NULL;
424 /* Make some search-and-replace in the html code */
425 string = g_string_sized_new (strlen (html) + strlen (message));
426 g_string_append_printf (string, "%s(\"", func);
427 for (cur = html; *cur != '\0'; cur++) {
428 const gchar *replace = NULL;
429 gchar *dup_replace = NULL;
430 gchar *format = NULL;
432 /* Those are all well known keywords that needs replacement in
433 * html files. Please keep them in the same order than the adium
434 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
435 if (theme_adium_match (&cur, "%userIconPath%")) {
436 replace = avatar_filename;
437 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
438 replace = contact_id;
439 } else if (theme_adium_match (&cur, "%sender%")) {
441 } else if (theme_adium_match (&cur, "%senderColor%")) {
442 /* A color derived from the user's name.
443 * FIXME: If a colon separated list of HTML colors is at
444 * Incoming/SenderColors.txt it will be used instead of
445 * the default colors.
447 if (contact_id != NULL) {
448 guint hash = g_str_hash (contact_id);
449 replace = colors[hash % G_N_ELEMENTS (colors)];
451 } else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
452 /* FIXME: The path to the status icon of the sender
453 * (available, away, etc...)
455 } else if (theme_adium_match (&cur, "%messageDirection%")) {
456 /* FIXME: The text direction of the message
457 * (either rtl or ltr)
459 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
460 /* FIXME: The serverside (remotely set) name of the
461 * sender, such as an MSN display name.
463 * We don't have access to that yet so we use
464 * local alias instead.
467 } else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
468 /* FIXME: This keyword is used to represent the
469 * highlight background color. "X" is the opacity of the
470 * background, ranges from 0 to 1 and can be any decimal
473 } else if (theme_adium_match (&cur, "%message%")) {
475 } else if (theme_adium_match (&cur, "%time%") ||
476 theme_adium_match_with_format (&cur, "%time{", &format)) {
477 /* FIXME: format is not exactly strftime.
478 * See NSDateFormatter spec:
479 * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
482 dup_replace = empathy_time_to_string_local (timestamp,
483 format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
485 dup_replace = empathy_time_to_string_local (timestamp,
486 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
488 replace = dup_replace;
489 } else if (theme_adium_match (&cur, "%shortTime%")) {
490 dup_replace = empathy_time_to_string_local (timestamp,
491 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
492 replace = dup_replace;
493 } else if (theme_adium_match (&cur, "%service%")) {
494 replace = service_name;
495 } else if (theme_adium_match (&cur, "%variant%")) {
496 /* FIXME: The name of the active message style variant,
497 * with all spaces replaced with an underscore.
498 * A variant named "Alternating Messages - Blue Red"
499 * will become "Alternating_Messages_-_Blue_Red".
501 } else if (theme_adium_match (&cur, "%userIcons%")) {
502 /* FIXME: mus t be "hideIcons" if use preference is set
504 replace = "showIcons";
505 } else if (theme_adium_match (&cur, "%messageClasses%")) {
506 replace = message_classes;
507 } else if (theme_adium_match (&cur, "%status%")) {
508 /* FIXME: A description of the status event. This is
509 * neither in the user's local language nor expected to
510 * be displayed; it may be useful to use a different div
511 * class to present different types of status messages.
512 * The following is a list of some of the more important
513 * status messages; your message style should be able to
514 * handle being shown a status message not in this list,
515 * as even at present the list is incomplete and is
516 * certain to become out of date in the future:
525 * contact_joined (group chats)
529 * encryption (all OTR messages use this status)
530 * purple (all IRC topic and join/part messages use this status)
531 * fileTransferStarted
532 * fileTransferCompleted
535 escape_and_append_len (string, cur, 1);
539 /* Here we have a replacement to make */
540 escape_and_append_len (string, replace, -1);
542 g_free (dup_replace);
545 g_string_append (string, "\")");
547 script = g_string_free (string, FALSE);
548 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
553 theme_adium_append_event_escaped (EmpathyChatView *view,
554 const gchar *escaped)
556 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
557 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
559 theme_adium_append_html (theme, "appendMessage",
560 priv->data->status_html, escaped, NULL, NULL, NULL,
562 empathy_time_get_current (), FALSE);
564 /* There is no last contact */
565 if (priv->last_contact) {
566 g_object_unref (priv->last_contact);
567 priv->last_contact = NULL;
572 theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
574 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
575 WebKitDOMDocument *dom;
576 WebKitDOMNodeList *nodes;
578 GError *error = NULL;
580 if (!priv->has_unread_message)
583 priv->has_unread_message = FALSE;
585 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
590 /* Get all nodes with focus class */
591 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
593 DEBUG ("Error getting focus nodes: %s",
594 error ? error->message : "No error");
595 g_clear_error (&error);
599 /* Remove focus and firstFocus class */
600 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
601 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
602 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
604 gchar **classes, **iter;
605 GString *new_class_name;
606 gboolean first = TRUE;
608 if (element == NULL) {
612 class_name = webkit_dom_html_element_get_class_name (element);
613 classes = g_strsplit (class_name, " ", -1);
614 new_class_name = g_string_sized_new (strlen (class_name));
615 for (iter = classes; *iter != NULL; iter++) {
616 if (tp_strdiff (*iter, "focus") &&
617 tp_strdiff (*iter, "firstFocus")) {
619 g_string_append_c (new_class_name, ' ');
621 g_string_append (new_class_name, *iter);
626 webkit_dom_html_element_set_class_name (element, new_class_name->str);
629 g_strfreev (classes);
630 g_string_free (new_class_name, TRUE);
635 theme_adium_append_message (EmpathyChatView *view,
638 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
639 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
640 EmpathyContact *sender;
646 const gchar *contact_id;
647 EmpathyAvatar *avatar;
648 const gchar *avatar_filename = NULL;
650 const gchar *html = NULL;
652 const gchar *service_name;
653 GString *message_classes = NULL;
655 gboolean consecutive;
658 if (priv->pages_loading != 0) {
659 GValue *value = tp_g_value_slice_new (EMPATHY_TYPE_MESSAGE);
660 g_value_set_object (value, msg);
661 g_queue_push_tail (&priv->message_queue, value);
665 /* Get information */
666 sender = empathy_message_get_sender (msg);
667 account = empathy_contact_get_account (sender);
668 service_name = empathy_protocol_name_to_display_name
669 (tp_account_get_protocol (account));
670 if (service_name == NULL)
671 service_name = tp_account_get_protocol (account);
672 timestamp = empathy_message_get_timestamp (msg);
673 body = empathy_message_get_body (msg);
674 body_escaped = theme_adium_parse_body (theme, body);
675 name = empathy_contact_get_alias (sender);
676 contact_id = empathy_contact_get_id (sender);
677 action = (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
679 /* If this is a /me probably */
683 if (priv->data->version >= 4 || !priv->data->custom_template) {
684 str = g_strdup_printf ("<span class='actionMessageUserName'>%s</span>"
685 "<span class='actionMessageBody'>%s</span>",
688 str = g_strdup_printf ("*%s*", body_escaped);
690 g_free (body_escaped);
694 /* Get the avatar filename, or a fallback */
695 avatar = empathy_contact_get_avatar (sender);
697 avatar_filename = avatar->filename;
699 if (!avatar_filename) {
700 if (empathy_contact_is_user (sender)) {
701 avatar_filename = priv->data->default_outgoing_avatar_filename;
703 avatar_filename = priv->data->default_incoming_avatar_filename;
705 if (!avatar_filename) {
706 if (!priv->data->default_avatar_filename) {
707 priv->data->default_avatar_filename =
708 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
709 GTK_ICON_SIZE_DIALOG);
711 avatar_filename = priv->data->default_avatar_filename;
715 /* We want to join this message with the last one if
716 * - senders are the same contact,
717 * - last message was recieved recently,
718 * - last message and this message both are/aren't backlog, and
719 * - DisableCombineConsecutive is not set in theme's settings */
720 is_backlog = empathy_message_is_backlog (msg);
721 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
722 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
723 (is_backlog == priv->last_is_backlog) &&
724 !tp_asv_get_boolean (priv->data->info,
725 "DisableCombineConsecutive", NULL);
727 /* Define message classes */
728 message_classes = g_string_new ("message");
729 if (!priv->has_focus && !is_backlog) {
730 if (!priv->has_unread_message) {
731 g_string_append (message_classes, " firstFocus");
732 priv->has_unread_message = TRUE;
734 g_string_append (message_classes, " focus");
737 g_string_append (message_classes, " history");
740 g_string_append (message_classes, " consecutive");
742 if (empathy_contact_is_user (sender)) {
743 g_string_append (message_classes, " outgoing");
745 g_string_append (message_classes, " incoming");
747 if (empathy_message_should_highlight (msg)) {
748 g_string_append (message_classes, " mention");
750 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
751 g_string_append (message_classes, " autoreply");
754 g_string_append (message_classes, " action");
756 /* FIXME: other classes:
757 * status - the message is a status change
758 * event - the message is a notification of something happening
759 * (for example, encryption being turned on)
760 * %status% - See %status% in theme_adium_append_html ()
763 /* x-empathy-message-id-* */
764 tp_msg = empathy_message_get_tp_message (msg);
765 if (tp_msg != NULL) {
766 gchar *tmp = tp_escape_as_identifier (
767 tp_message_get_token (tp_msg));
768 g_string_append_printf (message_classes,
769 " x-empathy-message-id-%s", tmp);
773 /* Define javascript function to use */
775 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
777 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
780 if (empathy_contact_is_user (sender)) {
784 html = consecutive ? priv->data->out_nextcontext_html : priv->data->out_context_html;
787 html = consecutive ? priv->data->out_nextcontent_html : priv->data->out_content_html;
790 /* remove all the unread marks when we are sending a message */
791 theme_adium_remove_focus_marks (theme);
796 html = consecutive ? priv->data->in_nextcontext_html : priv->data->in_context_html;
799 html = consecutive ? priv->data->in_nextcontent_html : priv->data->in_content_html;
803 theme_adium_append_html (theme, func, html, body_escaped,
804 avatar_filename, name, contact_id,
805 service_name, message_classes->str,
806 timestamp, is_backlog);
808 /* Keep the sender of the last displayed message */
809 if (priv->last_contact) {
810 g_object_unref (priv->last_contact);
812 priv->last_contact = g_object_ref (sender);
813 priv->last_timestamp = timestamp;
814 priv->last_is_backlog = is_backlog;
816 g_free (body_escaped);
817 g_string_free (message_classes, TRUE);
821 theme_adium_append_event (EmpathyChatView *view,
824 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
827 if (priv->pages_loading != 0) {
828 g_queue_push_tail (&priv->message_queue,
829 tp_g_value_slice_new_string (str));
833 str_escaped = g_markup_escape_text (str, -1);
834 theme_adium_append_event_escaped (view, str_escaped);
835 g_free (str_escaped);
839 theme_adium_scroll (EmpathyChatView *view,
840 gboolean allow_scrolling)
842 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
844 priv->allow_scrolling = allow_scrolling;
845 if (allow_scrolling) {
846 empathy_chat_view_scroll_down (view);
851 theme_adium_scroll_down (EmpathyChatView *view)
853 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
857 theme_adium_get_has_selection (EmpathyChatView *view)
859 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
863 theme_adium_clear (EmpathyChatView *view)
865 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
868 priv->pages_loading++;
869 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
870 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
871 priv->data->template_html,
873 g_free (basedir_uri);
875 /* Clear last contact to avoid trying to add a 'joined'
876 * message when we don't have an insertion point. */
877 if (priv->last_contact) {
878 g_object_unref (priv->last_contact);
879 priv->last_contact = NULL;
884 theme_adium_find_previous (EmpathyChatView *view,
885 const gchar *search_criteria,
889 /* FIXME: Doesn't respect new_search */
890 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
891 search_criteria, match_case,
896 theme_adium_find_next (EmpathyChatView *view,
897 const gchar *search_criteria,
901 /* FIXME: Doesn't respect new_search */
902 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
903 search_criteria, match_case,
908 theme_adium_find_abilities (EmpathyChatView *view,
909 const gchar *search_criteria,
911 gboolean *can_do_previous,
912 gboolean *can_do_next)
914 /* FIXME: Does webkit provide an API for that? We have wrap=true in
915 * find_next and find_previous to work around this problem. */
917 *can_do_previous = TRUE;
923 theme_adium_highlight (EmpathyChatView *view,
927 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
928 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
929 text, match_case, 0);
930 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
935 theme_adium_copy_clipboard (EmpathyChatView *view)
937 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
941 theme_adium_focus_toggled (EmpathyChatView *view,
944 EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
945 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
947 priv->has_focus = has_focus;
948 if (!priv->has_focus) {
949 theme_adium_remove_focus_marks (self);
954 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
956 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
958 g_object_unref (hit_test_result);
962 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
964 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
965 WebKitHitTestResult *hit_test_result;
966 WebKitHitTestResultContext context;
970 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
971 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
974 menu = empathy_context_menu_new (GTK_WIDGET (view));
976 /* Select all item */
977 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
978 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
980 g_signal_connect_swapped (item, "activate",
981 G_CALLBACK (webkit_web_view_select_all),
985 if (webkit_web_view_can_copy_clipboard (view)) {
986 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
987 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
989 g_signal_connect_swapped (item, "activate",
990 G_CALLBACK (webkit_web_view_copy_clipboard),
994 /* Clear menu item */
995 item = gtk_separator_menu_item_new ();
996 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
998 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
999 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1001 g_signal_connect_swapped (item, "activate",
1002 G_CALLBACK (empathy_chat_view_clear),
1005 /* We will only add the following menu items if we are
1006 * right-clicking a link */
1007 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1009 item = gtk_separator_menu_item_new ();
1010 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1012 /* Copy Link Address menu item */
1013 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1014 g_signal_connect (item, "activate",
1015 G_CALLBACK (theme_adium_copy_address_cb),
1017 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1019 /* Open Link menu item */
1020 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1021 g_signal_connect (item, "activate",
1022 G_CALLBACK (theme_adium_open_address_cb),
1024 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1027 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1028 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1031 /* Display the menu */
1032 gtk_widget_show_all (menu);
1033 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1034 event->button, event->time);
1038 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1040 if (event->button == 3) {
1041 gboolean developer_tools_enabled;
1043 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1044 "enable-developer-extras", &developer_tools_enabled, NULL);
1046 /* We currently have no way to add an inspector menu
1047 * item ourselves, so we disable our customized menu
1048 * if the developer extras are enabled. */
1049 if (!developer_tools_enabled) {
1050 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1055 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1059 theme_adium_iface_init (EmpathyChatViewIface *iface)
1061 iface->append_message = theme_adium_append_message;
1062 iface->append_event = theme_adium_append_event;
1063 iface->scroll = theme_adium_scroll;
1064 iface->scroll_down = theme_adium_scroll_down;
1065 iface->get_has_selection = theme_adium_get_has_selection;
1066 iface->clear = theme_adium_clear;
1067 iface->find_previous = theme_adium_find_previous;
1068 iface->find_next = theme_adium_find_next;
1069 iface->find_abilities = theme_adium_find_abilities;
1070 iface->highlight = theme_adium_highlight;
1071 iface->copy_clipboard = theme_adium_copy_clipboard;
1072 iface->focus_toggled = theme_adium_focus_toggled;
1076 theme_adium_load_finished_cb (WebKitWebView *view,
1077 WebKitWebFrame *frame,
1080 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1081 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1084 DEBUG ("Page loaded");
1085 priv->pages_loading--;
1087 if (priv->pages_loading != 0)
1090 /* Display queued messages */
1091 for (l = priv->message_queue.head; l != NULL; l = l->next) {
1092 GValue *value = l->data;
1094 if (G_VALUE_HOLDS_OBJECT (value)) {
1095 theme_adium_append_message (chat_view,
1096 g_value_get_object (value));
1098 theme_adium_append_event (chat_view,
1099 g_value_get_string (value));
1102 tp_g_value_slice_free (value);
1104 g_queue_clear (&priv->message_queue);
1108 theme_adium_finalize (GObject *object)
1110 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1112 empathy_adium_data_unref (priv->data);
1113 g_object_unref (priv->gsettings_chat);
1115 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1119 theme_adium_dispose (GObject *object)
1121 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1123 if (priv->smiley_manager) {
1124 g_object_unref (priv->smiley_manager);
1125 priv->smiley_manager = NULL;
1128 if (priv->last_contact) {
1129 g_object_unref (priv->last_contact);
1130 priv->last_contact = NULL;
1133 if (priv->inspector_window) {
1134 gtk_widget_destroy (priv->inspector_window);
1135 priv->inspector_window = NULL;
1138 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1142 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1143 EmpathyThemeAdium *theme)
1145 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1147 if (priv->inspector_window) {
1148 gtk_widget_show_all (priv->inspector_window);
1155 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1156 EmpathyThemeAdium *theme)
1158 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1160 if (priv->inspector_window) {
1161 gtk_widget_hide (priv->inspector_window);
1167 static WebKitWebView *
1168 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1169 WebKitWebView *web_view,
1170 EmpathyThemeAdium *theme)
1172 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1173 GtkWidget *scrolled_window;
1174 GtkWidget *inspector_web_view;
1176 if (!priv->inspector_window) {
1177 /* Create main window */
1178 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1179 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1181 g_signal_connect (priv->inspector_window, "delete-event",
1182 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1184 /* Pack a scrolled window */
1185 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1186 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1187 GTK_POLICY_AUTOMATIC,
1188 GTK_POLICY_AUTOMATIC);
1189 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1191 gtk_widget_show (scrolled_window);
1193 /* Pack a webview in the scrolled window. That webview will be
1194 * used to render the inspector tool. */
1195 inspector_web_view = webkit_web_view_new ();
1196 gtk_container_add (GTK_CONTAINER (scrolled_window),
1197 inspector_web_view);
1198 gtk_widget_show (scrolled_window);
1200 return WEBKIT_WEB_VIEW (inspector_web_view);
1206 static PangoFontDescription *
1207 theme_adium_get_default_font (void)
1209 GSettings *gsettings;
1210 PangoFontDescription *pango_fd;
1213 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1215 font_family = g_settings_get_string (gsettings,
1216 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1218 if (font_family == NULL)
1221 pango_fd = pango_font_description_from_string (font_family);
1222 g_free (font_family);
1223 g_object_unref (gsettings);
1228 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1232 g_object_set (w_settings, "default-font-family", name, NULL);
1233 g_object_set (w_settings, "default-font-size", size, NULL);
1237 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1239 PangoFontDescription *default_font_desc;
1240 GdkScreen *current_screen;
1242 gint pango_font_size = 0;
1244 default_font_desc = theme_adium_get_default_font ();
1245 if (default_font_desc == NULL)
1247 pango_font_size = pango_font_description_get_size (default_font_desc)
1249 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1250 current_screen = gdk_screen_get_default ();
1251 if (current_screen != NULL) {
1252 dpi = gdk_screen_get_resolution (current_screen);
1254 dpi = BORING_DPI_DEFAULT;
1256 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1258 theme_adium_set_webkit_font (w_settings,
1259 pango_font_description_get_family (default_font_desc),
1261 pango_font_description_free (default_font_desc);
1265 theme_adium_constructed (GObject *object)
1267 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1269 const gchar *font_family = NULL;
1271 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1272 WebKitWebSettings *webkit_settings;
1273 WebKitWebInspector *webkit_inspector;
1275 /* Set default settings */
1276 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1277 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1278 webkit_settings = webkit_web_view_get_settings (webkit_view);
1280 if (font_family && font_size) {
1281 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1283 theme_adium_set_default_font (webkit_settings);
1286 /* Setup webkit inspector */
1287 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1288 g_signal_connect (webkit_inspector, "inspect-web-view",
1289 G_CALLBACK (theme_adium_inspect_web_view_cb),
1291 g_signal_connect (webkit_inspector, "show-window",
1292 G_CALLBACK (theme_adium_inspector_show_window_cb),
1294 g_signal_connect (webkit_inspector, "close-window",
1295 G_CALLBACK (theme_adium_inspector_close_window_cb),
1299 priv->pages_loading = 1;
1301 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1302 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1303 priv->data->template_html,
1305 g_free (basedir_uri);
1309 theme_adium_get_property (GObject *object,
1314 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1317 case PROP_ADIUM_DATA:
1318 g_value_set_boxed (value, priv->data);
1321 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1327 theme_adium_set_property (GObject *object,
1329 const GValue *value,
1332 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1335 case PROP_ADIUM_DATA:
1336 g_assert (priv->data == NULL);
1337 priv->data = g_value_dup_boxed (value);
1340 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1346 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1348 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1349 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1351 object_class->finalize = theme_adium_finalize;
1352 object_class->dispose = theme_adium_dispose;
1353 object_class->constructed = theme_adium_constructed;
1354 object_class->get_property = theme_adium_get_property;
1355 object_class->set_property = theme_adium_set_property;
1357 widget_class->button_press_event = theme_adium_button_press_event;
1359 g_object_class_install_property (object_class,
1361 g_param_spec_boxed ("adium-data",
1363 "Data for the adium theme",
1364 EMPATHY_TYPE_ADIUM_DATA,
1365 G_PARAM_CONSTRUCT_ONLY |
1367 G_PARAM_STATIC_STRINGS));
1369 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1373 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1375 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1376 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1380 g_queue_init (&priv->message_queue);
1381 priv->allow_scrolling = TRUE;
1382 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1384 g_signal_connect (theme, "load-finished",
1385 G_CALLBACK (theme_adium_load_finished_cb),
1387 g_signal_connect (theme, "navigation-policy-decision-requested",
1388 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1391 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1392 g_signal_connect (priv->gsettings_chat,
1393 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1394 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1397 theme_adium_update_enable_webkit_developer_tools (theme);
1401 empathy_theme_adium_new (EmpathyAdiumData *data)
1403 g_return_val_if_fail (data != NULL, NULL);
1405 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1411 empathy_adium_path_is_valid (const gchar *path)
1416 /* The theme is not valid if there is no Info.plist */
1417 file = g_build_filename (path, "Contents", "Info.plist",
1419 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1425 /* We ship a default Template.html as fallback if there is any problem
1426 * with the one inside the theme. The only other required file is
1427 * Content.html OR Incoming/Content.html*/
1428 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1430 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1434 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1435 "Content.html", NULL);
1436 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1444 empathy_adium_info_new (const gchar *path)
1448 GHashTable *info = NULL;
1450 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1452 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1453 value = empathy_plist_parse_from_file (file);
1459 info = g_value_dup_boxed (value);
1460 tp_g_value_slice_free (value);
1462 /* Insert the theme's path into the hash table,
1463 * keys have to be dupped */
1464 tp_asv_set_string (info, g_strdup ("path"), path);
1470 adium_info_get_version (GHashTable *info)
1472 return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
1475 static const gchar *
1476 adium_info_get_no_variant_name (GHashTable *info)
1478 const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
1479 return name ? name : _("Normal");
1482 static const gchar *
1483 adium_info_get_default_or_first_variant (GHashTable *info)
1486 GPtrArray *variants;
1488 name = empathy_adium_info_get_default_variant (info);
1493 variants = empathy_adium_info_get_available_variants (info);
1494 g_assert (variants->len > 0);
1495 return g_ptr_array_index (variants, 0);
1499 adium_info_dup_path_for_variant (GHashTable *info,
1500 const gchar *variant)
1502 guint version = adium_info_get_version (info);
1503 const gchar *no_variant = adium_info_get_no_variant_name (info);
1505 if (version <= 2 && !tp_strdiff (variant, no_variant)) {
1506 return g_strdup ("main.css");
1509 return g_strdup_printf ("Variants/%s.css", variant);
1514 empathy_adium_info_get_default_variant (GHashTable *info)
1516 if (adium_info_get_version (info) <= 2) {
1517 return adium_info_get_no_variant_name (info);
1520 return tp_asv_get_string (info, "DefaultVariant");
1524 empathy_adium_info_get_available_variants (GHashTable *info)
1526 GPtrArray *variants;
1531 variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
1532 if (variants != NULL) {
1536 variants = g_ptr_array_new_with_free_func (g_free);
1537 tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
1538 G_TYPE_PTR_ARRAY, variants);
1540 path = tp_asv_get_string (info, "path");
1541 dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
1542 dir = g_dir_open (dirpath, 0, NULL);
1546 for (name = g_dir_read_name (dir);
1548 name = g_dir_read_name (dir)) {
1549 gchar *display_name;
1551 if (!g_str_has_suffix (name, ".css")) {
1555 display_name = g_strdup (name);
1556 strstr (display_name, ".css")[0] = '\0';
1557 g_ptr_array_add (variants, display_name);
1563 if (adium_info_get_version (info) <= 2) {
1564 g_ptr_array_add (variants,
1565 g_strdup (adium_info_get_no_variant_name (info)));
1572 empathy_adium_data_get_type (void)
1574 static GType type_id = 0;
1578 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1579 (GBoxedCopyFunc) empathy_adium_data_ref,
1580 (GBoxedFreeFunc) empathy_adium_data_unref);
1587 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1589 EmpathyAdiumData *data;
1590 gchar *template_html = NULL;
1591 gchar *footer_html = NULL;
1592 gchar *variant_path;
1595 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1597 data = g_slice_new0 (EmpathyAdiumData);
1598 data->ref_count = 1;
1599 data->path = g_strdup (path);
1600 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1601 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1602 data->info = g_hash_table_ref (info);
1603 data->version = adium_info_get_version (info);
1604 data->strings_to_free = g_ptr_array_new_with_free_func (g_free);
1606 DEBUG ("Loading theme at %s", path);
1608 #define LOAD(path, var) \
1609 tmp = g_build_filename (data->basedir, path, NULL); \
1610 g_file_get_contents (tmp, &var, NULL, NULL); \
1613 #define LOAD_CONST(path, var) \
1616 LOAD (path, content); \
1617 if (content != NULL) { \
1618 g_ptr_array_add (data->strings_to_free, content); \
1623 /* Load html files */
1624 LOAD_CONST ("Content.html", data->content_html);
1625 LOAD_CONST ("Incoming/Content.html", data->in_content_html);
1626 LOAD_CONST ("Incoming/NextContent.html", data->in_nextcontent_html);
1627 LOAD_CONST ("Incoming/Context.html", data->in_context_html);
1628 LOAD_CONST ("Incoming/NextContext.html", data->in_nextcontext_html);
1629 LOAD_CONST ("Outgoing/Content.html", data->out_content_html);
1630 LOAD_CONST ("Outgoing/NextContent.html", data->out_nextcontent_html);
1631 LOAD_CONST ("Outgoing/Context.html", data->out_context_html);
1632 LOAD_CONST ("Outgoing/NextContext.html", data->out_nextcontext_html);
1633 LOAD_CONST ("Status.html", data->status_html);
1634 LOAD ("Template.html", template_html);
1635 LOAD ("Footer.html", footer_html);
1640 /* HTML fallbacks: If we have at least content OR in_content, then
1641 * everything else gets a fallback */
1643 #define FALLBACK(html, fallback) \
1644 if (html == NULL) { \
1648 /* in_nextcontent -> in_content -> content */
1649 FALLBACK (data->in_content_html, data->content_html);
1650 FALLBACK (data->in_nextcontent_html, data->in_content_html);
1652 /* context -> content */
1653 FALLBACK (data->in_context_html, data->in_content_html);
1654 FALLBACK (data->in_nextcontext_html, data->in_nextcontent_html);
1655 FALLBACK (data->out_context_html, data->out_content_html);
1656 FALLBACK (data->out_nextcontext_html, data->out_nextcontent_html);
1659 FALLBACK (data->out_content_html, data->in_content_html);
1660 FALLBACK (data->out_nextcontent_html, data->in_nextcontent_html);
1661 FALLBACK (data->out_context_html, data->in_context_html);
1662 FALLBACK (data->out_nextcontext_html, data->in_nextcontext_html);
1664 /* status -> in_content */
1665 FALLBACK (data->status_html, data->in_content_html);
1669 /* template -> empathy's template */
1670 data->custom_template = (template_html != NULL);
1671 if (template_html == NULL) {
1672 tmp = empathy_file_lookup ("Template.html", "data");
1673 g_file_get_contents (tmp, &template_html, NULL, NULL);
1677 /* Default avatar */
1678 tmp = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1679 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1680 data->default_incoming_avatar_filename = tmp;
1684 tmp = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1685 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1686 data->default_outgoing_avatar_filename = tmp;
1691 variant_path = adium_info_dup_path_for_variant (info,
1692 adium_info_get_default_or_first_variant (info));
1694 /* Old custom templates had only 4 parameters.
1695 * New templates have 5 parameters */
1696 if (data->version <= 2 && data->custom_template) {
1697 tmp = string_with_format (template_html,
1700 "", /* The header */
1701 footer_html ? footer_html : "",
1704 tmp = string_with_format (template_html,
1706 data->version <= 2 ? "" : "@import url( \"main.css\" );",
1708 "", /* The header */
1709 footer_html ? footer_html : "",
1712 g_ptr_array_add (data->strings_to_free, tmp);
1713 data->template_html = tmp;
1715 g_free (template_html);
1716 g_free (footer_html);
1717 g_free (variant_path);
1723 empathy_adium_data_new (const gchar *path)
1725 EmpathyAdiumData *data;
1728 info = empathy_adium_info_new (path);
1729 data = empathy_adium_data_new_with_info (path, info);
1730 g_hash_table_unref (info);
1736 empathy_adium_data_ref (EmpathyAdiumData *data)
1738 g_return_val_if_fail (data != NULL, NULL);
1740 g_atomic_int_inc (&data->ref_count);
1746 empathy_adium_data_unref (EmpathyAdiumData *data)
1748 g_return_if_fail (data != NULL);
1750 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1751 g_free (data->path);
1752 g_free (data->basedir);
1753 g_free (data->default_avatar_filename);
1754 g_free (data->default_incoming_avatar_filename);
1755 g_free (data->default_outgoing_avatar_filename);
1756 g_hash_table_unref (data->info);
1757 g_ptr_array_unref (data->strings_to_free);
1759 g_slice_free (EmpathyAdiumData, data);
1764 empathy_adium_data_get_info (EmpathyAdiumData *data)
1766 g_return_val_if_fail (data != NULL, NULL);
1772 empathy_adium_data_get_path (EmpathyAdiumData *data)
1774 g_return_val_if_fail (data != NULL, NULL);