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;
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 WebKitDOMDocument *dom;
575 WebKitDOMNodeList *nodes;
577 GError *error = NULL;
579 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
584 /* Get all nodes with focus class */
585 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
587 DEBUG ("Error getting focus nodes: %s",
588 error ? error->message : "No error");
589 g_clear_error (&error);
593 /* Remove focus and firstFocus class */
594 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
595 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
596 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
598 gchar **classes, **iter;
599 GString *new_class_name;
600 gboolean first = TRUE;
602 if (element == NULL) {
606 class_name = webkit_dom_html_element_get_class_name (element);
607 classes = g_strsplit (class_name, " ", -1);
608 new_class_name = g_string_sized_new (strlen (class_name));
609 for (iter = classes; *iter != NULL; iter++) {
610 if (tp_strdiff (*iter, "focus") &&
611 tp_strdiff (*iter, "firstFocus")) {
613 g_string_append_c (new_class_name, ' ');
615 g_string_append (new_class_name, *iter);
620 webkit_dom_html_element_set_class_name (element, new_class_name->str);
623 g_strfreev (classes);
624 g_string_free (new_class_name, TRUE);
629 theme_adium_append_message (EmpathyChatView *view,
632 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
633 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
634 EmpathyContact *sender;
639 const gchar *contact_id;
640 EmpathyAvatar *avatar;
641 const gchar *avatar_filename = NULL;
643 const gchar *html = NULL;
645 const gchar *service_name;
646 GString *message_classes = NULL;
648 gboolean consecutive;
651 if (priv->pages_loading != 0) {
652 GValue *value = tp_g_value_slice_new (EMPATHY_TYPE_MESSAGE);
653 g_value_set_object (value, msg);
654 g_queue_push_tail (&priv->message_queue, value);
658 /* Get information */
659 sender = empathy_message_get_sender (msg);
660 account = empathy_contact_get_account (sender);
661 service_name = empathy_protocol_name_to_display_name
662 (tp_account_get_protocol (account));
663 if (service_name == NULL)
664 service_name = tp_account_get_protocol (account);
665 timestamp = empathy_message_get_timestamp (msg);
666 body = empathy_message_get_body (msg);
667 body_escaped = theme_adium_parse_body (theme, body);
668 name = empathy_contact_get_alias (sender);
669 contact_id = empathy_contact_get_id (sender);
670 action = (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
672 /* If this is a /me probably */
676 if (priv->data->version >= 4 || !priv->data->custom_template) {
677 str = g_strdup_printf ("<span class='actionMessageUserName'>%s</span>"
678 "<span class='actionMessageBody'>%s</span>",
681 str = g_strdup_printf ("*%s*", body_escaped);
683 g_free (body_escaped);
687 /* Get the avatar filename, or a fallback */
688 avatar = empathy_contact_get_avatar (sender);
690 avatar_filename = avatar->filename;
692 if (!avatar_filename) {
693 if (empathy_contact_is_user (sender)) {
694 avatar_filename = priv->data->default_outgoing_avatar_filename;
696 avatar_filename = priv->data->default_incoming_avatar_filename;
698 if (!avatar_filename) {
699 if (!priv->data->default_avatar_filename) {
700 priv->data->default_avatar_filename =
701 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
702 GTK_ICON_SIZE_DIALOG);
704 avatar_filename = priv->data->default_avatar_filename;
708 /* We want to join this message with the last one if
709 * - senders are the same contact,
710 * - last message was recieved recently,
711 * - last message and this message both are/aren't backlog, and
712 * - DisableCombineConsecutive is not set in theme's settings */
713 is_backlog = empathy_message_is_backlog (msg);
714 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
715 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
716 (is_backlog == priv->last_is_backlog) &&
717 !tp_asv_get_boolean (priv->data->info,
718 "DisableCombineConsecutive", NULL);
720 /* Define message classes */
721 message_classes = g_string_new ("message");
722 if (!priv->has_focus && !is_backlog) {
723 if (!priv->has_unread_message) {
724 /* This is the first message we receive since we lost
725 * focus; remove previous unread marks. */
726 theme_adium_remove_focus_marks (theme);
728 g_string_append (message_classes, " firstFocus");
729 priv->has_unread_message = TRUE;
731 g_string_append (message_classes, " focus");
734 g_string_append (message_classes, " history");
737 g_string_append (message_classes, " consecutive");
739 if (empathy_contact_is_user (sender)) {
740 g_string_append (message_classes, " outgoing");
742 g_string_append (message_classes, " incoming");
744 if (empathy_message_should_highlight (msg)) {
745 g_string_append (message_classes, " mention");
747 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
748 g_string_append (message_classes, " autoreply");
751 g_string_append (message_classes, " action");
753 /* FIXME: other classes:
754 * status - the message is a status change
755 * event - the message is a notification of something happening
756 * (for example, encryption being turned on)
757 * %status% - See %status% in theme_adium_append_html ()
760 /* Define javascript function to use */
762 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
764 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
767 if (empathy_contact_is_user (sender)) {
771 html = consecutive ? priv->data->out_nextcontext_html : priv->data->out_context_html;
774 html = consecutive ? priv->data->out_nextcontent_html : priv->data->out_content_html;
780 html = consecutive ? priv->data->in_nextcontext_html : priv->data->in_context_html;
783 html = consecutive ? priv->data->in_nextcontent_html : priv->data->in_content_html;
787 theme_adium_append_html (theme, func, html, body_escaped,
788 avatar_filename, name, contact_id,
789 service_name, message_classes->str,
790 timestamp, is_backlog);
792 /* Keep the sender of the last displayed message */
793 if (priv->last_contact) {
794 g_object_unref (priv->last_contact);
796 priv->last_contact = g_object_ref (sender);
797 priv->last_timestamp = timestamp;
798 priv->last_is_backlog = is_backlog;
800 g_free (body_escaped);
801 g_string_free (message_classes, TRUE);
805 theme_adium_append_event (EmpathyChatView *view,
808 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
811 if (priv->pages_loading != 0) {
812 g_queue_push_tail (&priv->message_queue,
813 tp_g_value_slice_new_string (str));
817 str_escaped = g_markup_escape_text (str, -1);
818 theme_adium_append_event_escaped (view, str_escaped);
819 g_free (str_escaped);
823 theme_adium_scroll (EmpathyChatView *view,
824 gboolean allow_scrolling)
826 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
828 priv->allow_scrolling = allow_scrolling;
829 if (allow_scrolling) {
830 empathy_chat_view_scroll_down (view);
835 theme_adium_scroll_down (EmpathyChatView *view)
837 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
841 theme_adium_get_has_selection (EmpathyChatView *view)
843 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
847 theme_adium_clear (EmpathyChatView *view)
849 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
852 priv->pages_loading++;
853 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
854 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
855 priv->data->template_html,
857 g_free (basedir_uri);
859 /* Clear last contact to avoid trying to add a 'joined'
860 * message when we don't have an insertion point. */
861 if (priv->last_contact) {
862 g_object_unref (priv->last_contact);
863 priv->last_contact = NULL;
868 theme_adium_find_previous (EmpathyChatView *view,
869 const gchar *search_criteria,
873 /* FIXME: Doesn't respect new_search */
874 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
875 search_criteria, match_case,
880 theme_adium_find_next (EmpathyChatView *view,
881 const gchar *search_criteria,
885 /* FIXME: Doesn't respect new_search */
886 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
887 search_criteria, match_case,
892 theme_adium_find_abilities (EmpathyChatView *view,
893 const gchar *search_criteria,
895 gboolean *can_do_previous,
896 gboolean *can_do_next)
898 /* FIXME: Does webkit provide an API for that? We have wrap=true in
899 * find_next and find_previous to work around this problem. */
901 *can_do_previous = TRUE;
907 theme_adium_highlight (EmpathyChatView *view,
911 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
912 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
913 text, match_case, 0);
914 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
919 theme_adium_copy_clipboard (EmpathyChatView *view)
921 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
925 theme_adium_focus_toggled (EmpathyChatView *view,
928 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
930 priv->has_focus = has_focus;
931 if (priv->has_focus) {
932 priv->has_unread_message = FALSE;
937 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
939 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
941 g_object_unref (hit_test_result);
945 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
947 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
948 WebKitHitTestResult *hit_test_result;
949 WebKitHitTestResultContext context;
953 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
954 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
957 menu = empathy_context_menu_new (GTK_WIDGET (view));
959 /* Select all item */
960 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
961 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
963 g_signal_connect_swapped (item, "activate",
964 G_CALLBACK (webkit_web_view_select_all),
968 if (webkit_web_view_can_copy_clipboard (view)) {
969 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
970 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
972 g_signal_connect_swapped (item, "activate",
973 G_CALLBACK (webkit_web_view_copy_clipboard),
977 /* Clear menu item */
978 item = gtk_separator_menu_item_new ();
979 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
981 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
982 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
984 g_signal_connect_swapped (item, "activate",
985 G_CALLBACK (empathy_chat_view_clear),
988 /* We will only add the following menu items if we are
989 * right-clicking a link */
990 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
992 item = gtk_separator_menu_item_new ();
993 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
995 /* Copy Link Address menu item */
996 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
997 g_signal_connect (item, "activate",
998 G_CALLBACK (theme_adium_copy_address_cb),
1000 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1002 /* Open Link menu item */
1003 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1004 g_signal_connect (item, "activate",
1005 G_CALLBACK (theme_adium_open_address_cb),
1007 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1010 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1011 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1014 /* Display the menu */
1015 gtk_widget_show_all (menu);
1016 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1017 event->button, event->time);
1021 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1023 if (event->button == 3) {
1024 gboolean developer_tools_enabled;
1026 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1027 "enable-developer-extras", &developer_tools_enabled, NULL);
1029 /* We currently have no way to add an inspector menu
1030 * item ourselves, so we disable our customized menu
1031 * if the developer extras are enabled. */
1032 if (!developer_tools_enabled) {
1033 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1038 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1042 theme_adium_iface_init (EmpathyChatViewIface *iface)
1044 iface->append_message = theme_adium_append_message;
1045 iface->append_event = theme_adium_append_event;
1046 iface->scroll = theme_adium_scroll;
1047 iface->scroll_down = theme_adium_scroll_down;
1048 iface->get_has_selection = theme_adium_get_has_selection;
1049 iface->clear = theme_adium_clear;
1050 iface->find_previous = theme_adium_find_previous;
1051 iface->find_next = theme_adium_find_next;
1052 iface->find_abilities = theme_adium_find_abilities;
1053 iface->highlight = theme_adium_highlight;
1054 iface->copy_clipboard = theme_adium_copy_clipboard;
1055 iface->focus_toggled = theme_adium_focus_toggled;
1059 theme_adium_load_finished_cb (WebKitWebView *view,
1060 WebKitWebFrame *frame,
1063 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1064 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1067 DEBUG ("Page loaded");
1068 priv->pages_loading--;
1070 if (priv->pages_loading != 0)
1073 /* Display queued messages */
1074 for (l = priv->message_queue.head; l != NULL; l = l->next) {
1075 GValue *value = l->data;
1077 if (G_VALUE_HOLDS_OBJECT (value)) {
1078 theme_adium_append_message (chat_view,
1079 g_value_get_object (value));
1081 theme_adium_append_event (chat_view,
1082 g_value_get_string (value));
1085 tp_g_value_slice_free (value);
1087 g_queue_clear (&priv->message_queue);
1091 theme_adium_finalize (GObject *object)
1093 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1095 empathy_adium_data_unref (priv->data);
1096 g_object_unref (priv->gsettings_chat);
1098 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1102 theme_adium_dispose (GObject *object)
1104 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1106 if (priv->smiley_manager) {
1107 g_object_unref (priv->smiley_manager);
1108 priv->smiley_manager = NULL;
1111 if (priv->last_contact) {
1112 g_object_unref (priv->last_contact);
1113 priv->last_contact = NULL;
1116 if (priv->inspector_window) {
1117 gtk_widget_destroy (priv->inspector_window);
1118 priv->inspector_window = NULL;
1121 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1125 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1126 EmpathyThemeAdium *theme)
1128 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1130 if (priv->inspector_window) {
1131 gtk_widget_show_all (priv->inspector_window);
1138 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1139 EmpathyThemeAdium *theme)
1141 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1143 if (priv->inspector_window) {
1144 gtk_widget_hide (priv->inspector_window);
1150 static WebKitWebView *
1151 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1152 WebKitWebView *web_view,
1153 EmpathyThemeAdium *theme)
1155 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1156 GtkWidget *scrolled_window;
1157 GtkWidget *inspector_web_view;
1159 if (!priv->inspector_window) {
1160 /* Create main window */
1161 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1162 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1164 g_signal_connect (priv->inspector_window, "delete-event",
1165 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1167 /* Pack a scrolled window */
1168 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1169 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1170 GTK_POLICY_AUTOMATIC,
1171 GTK_POLICY_AUTOMATIC);
1172 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1174 gtk_widget_show (scrolled_window);
1176 /* Pack a webview in the scrolled window. That webview will be
1177 * used to render the inspector tool. */
1178 inspector_web_view = webkit_web_view_new ();
1179 gtk_container_add (GTK_CONTAINER (scrolled_window),
1180 inspector_web_view);
1181 gtk_widget_show (scrolled_window);
1183 return WEBKIT_WEB_VIEW (inspector_web_view);
1189 static PangoFontDescription *
1190 theme_adium_get_default_font (void)
1192 GSettings *gsettings;
1193 PangoFontDescription *pango_fd;
1196 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1198 font_family = g_settings_get_string (gsettings,
1199 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1201 if (font_family == NULL)
1204 pango_fd = pango_font_description_from_string (font_family);
1205 g_free (font_family);
1206 g_object_unref (gsettings);
1211 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1215 g_object_set (w_settings, "default-font-family", name, NULL);
1216 g_object_set (w_settings, "default-font-size", size, NULL);
1220 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1222 PangoFontDescription *default_font_desc;
1223 GdkScreen *current_screen;
1225 gint pango_font_size = 0;
1227 default_font_desc = theme_adium_get_default_font ();
1228 if (default_font_desc == NULL)
1230 pango_font_size = pango_font_description_get_size (default_font_desc)
1232 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1233 current_screen = gdk_screen_get_default ();
1234 if (current_screen != NULL) {
1235 dpi = gdk_screen_get_resolution (current_screen);
1237 dpi = BORING_DPI_DEFAULT;
1239 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1241 theme_adium_set_webkit_font (w_settings,
1242 pango_font_description_get_family (default_font_desc),
1244 pango_font_description_free (default_font_desc);
1248 theme_adium_constructed (GObject *object)
1250 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1252 const gchar *font_family = NULL;
1254 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1255 WebKitWebSettings *webkit_settings;
1256 WebKitWebInspector *webkit_inspector;
1258 /* Set default settings */
1259 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1260 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1261 webkit_settings = webkit_web_view_get_settings (webkit_view);
1263 if (font_family && font_size) {
1264 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1266 theme_adium_set_default_font (webkit_settings);
1269 /* Setup webkit inspector */
1270 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1271 g_signal_connect (webkit_inspector, "inspect-web-view",
1272 G_CALLBACK (theme_adium_inspect_web_view_cb),
1274 g_signal_connect (webkit_inspector, "show-window",
1275 G_CALLBACK (theme_adium_inspector_show_window_cb),
1277 g_signal_connect (webkit_inspector, "close-window",
1278 G_CALLBACK (theme_adium_inspector_close_window_cb),
1282 priv->pages_loading = 1;
1284 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1285 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1286 priv->data->template_html,
1288 g_free (basedir_uri);
1292 theme_adium_get_property (GObject *object,
1297 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1300 case PROP_ADIUM_DATA:
1301 g_value_set_boxed (value, priv->data);
1304 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1310 theme_adium_set_property (GObject *object,
1312 const GValue *value,
1315 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1318 case PROP_ADIUM_DATA:
1319 g_assert (priv->data == NULL);
1320 priv->data = g_value_dup_boxed (value);
1323 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1329 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1331 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1332 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1334 object_class->finalize = theme_adium_finalize;
1335 object_class->dispose = theme_adium_dispose;
1336 object_class->constructed = theme_adium_constructed;
1337 object_class->get_property = theme_adium_get_property;
1338 object_class->set_property = theme_adium_set_property;
1340 widget_class->button_press_event = theme_adium_button_press_event;
1342 g_object_class_install_property (object_class,
1344 g_param_spec_boxed ("adium-data",
1346 "Data for the adium theme",
1347 EMPATHY_TYPE_ADIUM_DATA,
1348 G_PARAM_CONSTRUCT_ONLY |
1350 G_PARAM_STATIC_STRINGS));
1352 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1356 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1358 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1359 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1363 g_queue_init (&priv->message_queue);
1364 priv->allow_scrolling = TRUE;
1365 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1367 g_signal_connect (theme, "load-finished",
1368 G_CALLBACK (theme_adium_load_finished_cb),
1370 g_signal_connect (theme, "navigation-policy-decision-requested",
1371 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1374 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1375 g_signal_connect (priv->gsettings_chat,
1376 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1377 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1380 theme_adium_update_enable_webkit_developer_tools (theme);
1384 empathy_theme_adium_new (EmpathyAdiumData *data)
1386 g_return_val_if_fail (data != NULL, NULL);
1388 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1394 empathy_adium_path_is_valid (const gchar *path)
1399 /* The theme is not valid if there is no Info.plist */
1400 file = g_build_filename (path, "Contents", "Info.plist",
1402 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1408 /* We ship a default Template.html as fallback if there is any problem
1409 * with the one inside the theme. The only other required file is
1410 * Content.html OR Incoming/Content.html*/
1411 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1413 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1417 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1418 "Content.html", NULL);
1419 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1427 empathy_adium_info_new (const gchar *path)
1431 GHashTable *info = NULL;
1433 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1435 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1436 value = empathy_plist_parse_from_file (file);
1442 info = g_value_dup_boxed (value);
1443 tp_g_value_slice_free (value);
1445 /* Insert the theme's path into the hash table,
1446 * keys have to be dupped */
1447 tp_asv_set_string (info, g_strdup ("path"), path);
1453 adium_info_get_version (GHashTable *info)
1455 return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
1458 static const gchar *
1459 adium_info_get_no_variant_name (GHashTable *info)
1461 const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
1462 return name ? name : _("Normal");
1465 static const gchar *
1466 adium_info_get_default_or_first_variant (GHashTable *info)
1469 GPtrArray *variants;
1471 name = empathy_adium_info_get_default_variant (info);
1476 variants = empathy_adium_info_get_available_variants (info);
1477 g_assert (variants->len > 0);
1478 return g_ptr_array_index (variants, 0);
1482 adium_info_dup_path_for_variant (GHashTable *info,
1483 const gchar *variant)
1485 guint version = adium_info_get_version (info);
1486 const gchar *no_variant = adium_info_get_no_variant_name (info);
1488 if (version <= 2 && !tp_strdiff (variant, no_variant)) {
1489 return g_strdup ("main.css");
1492 return g_strdup_printf ("Variants/%s.css", variant);
1497 empathy_adium_info_get_default_variant (GHashTable *info)
1499 if (adium_info_get_version (info) <= 2) {
1500 return adium_info_get_no_variant_name (info);
1503 return tp_asv_get_string (info, "DefaultVariant");
1507 empathy_adium_info_get_available_variants (GHashTable *info)
1509 GPtrArray *variants;
1514 variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
1515 if (variants != NULL) {
1519 variants = g_ptr_array_new_with_free_func (g_free);
1520 tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
1521 G_TYPE_PTR_ARRAY, variants);
1523 path = tp_asv_get_string (info, "path");
1524 dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
1525 dir = g_dir_open (dirpath, 0, NULL);
1529 for (name = g_dir_read_name (dir);
1531 name = g_dir_read_name (dir)) {
1532 gchar *display_name;
1534 if (!g_str_has_suffix (name, ".css")) {
1538 display_name = g_strdup (name);
1539 strstr (display_name, ".css")[0] = '\0';
1540 g_ptr_array_add (variants, display_name);
1546 if (adium_info_get_version (info) <= 2) {
1547 g_ptr_array_add (variants,
1548 g_strdup (adium_info_get_no_variant_name (info)));
1555 empathy_adium_data_get_type (void)
1557 static GType type_id = 0;
1561 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1562 (GBoxedCopyFunc) empathy_adium_data_ref,
1563 (GBoxedFreeFunc) empathy_adium_data_unref);
1570 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1572 EmpathyAdiumData *data;
1573 gchar *template_html = NULL;
1574 gchar *footer_html = NULL;
1575 gchar *variant_path;
1578 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1580 data = g_slice_new0 (EmpathyAdiumData);
1581 data->ref_count = 1;
1582 data->path = g_strdup (path);
1583 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1584 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1585 data->info = g_hash_table_ref (info);
1586 data->version = adium_info_get_version (info);
1587 data->strings_to_free = g_ptr_array_new_with_free_func (g_free);
1589 DEBUG ("Loading theme at %s", path);
1591 #define LOAD(path, var) \
1592 tmp = g_build_filename (data->basedir, path, NULL); \
1593 g_file_get_contents (tmp, &var, NULL, NULL); \
1596 #define LOAD_CONST(path, var) \
1599 LOAD (path, content); \
1600 if (content != NULL) { \
1601 g_ptr_array_add (data->strings_to_free, content); \
1606 /* Load html files */
1607 LOAD_CONST ("Content.html", data->content_html);
1608 LOAD_CONST ("Incoming/Content.html", data->in_content_html);
1609 LOAD_CONST ("Incoming/NextContent.html", data->in_nextcontent_html);
1610 LOAD_CONST ("Incoming/Context.html", data->in_context_html);
1611 LOAD_CONST ("Incoming/NextContext.html", data->in_nextcontext_html);
1612 LOAD_CONST ("Outgoing/Content.html", data->out_content_html);
1613 LOAD_CONST ("Outgoing/NextContent.html", data->out_nextcontent_html);
1614 LOAD_CONST ("Outgoing/Context.html", data->out_context_html);
1615 LOAD_CONST ("Outgoing/NextContext.html", data->out_nextcontext_html);
1616 LOAD_CONST ("Status.html", data->status_html);
1617 LOAD ("Template.html", template_html);
1618 LOAD ("Footer.html", footer_html);
1623 /* HTML fallbacks: If we have at least content OR in_content, then
1624 * everything else gets a fallback */
1626 #define FALLBACK(html, fallback) \
1627 if (html == NULL) { \
1631 /* in_nextcontent -> in_content -> content */
1632 FALLBACK (data->in_content_html, data->content_html);
1633 FALLBACK (data->in_nextcontent_html, data->in_content_html);
1635 /* context -> content */
1636 FALLBACK (data->in_context_html, data->in_content_html);
1637 FALLBACK (data->in_nextcontext_html, data->in_nextcontent_html);
1638 FALLBACK (data->out_context_html, data->out_content_html);
1639 FALLBACK (data->out_nextcontext_html, data->out_nextcontent_html);
1642 FALLBACK (data->out_content_html, data->in_content_html);
1643 FALLBACK (data->out_nextcontent_html, data->in_nextcontent_html);
1644 FALLBACK (data->out_context_html, data->in_context_html);
1645 FALLBACK (data->out_nextcontext_html, data->in_nextcontext_html);
1647 /* status -> in_content */
1648 FALLBACK (data->status_html, data->in_content_html);
1652 /* template -> empathy's template */
1653 data->custom_template = (template_html != NULL);
1654 if (template_html == NULL) {
1655 tmp = empathy_file_lookup ("Template.html", "data");
1656 g_file_get_contents (tmp, &template_html, NULL, NULL);
1660 /* Default avatar */
1661 tmp = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1662 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1663 data->default_incoming_avatar_filename = tmp;
1667 tmp = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1668 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1669 data->default_outgoing_avatar_filename = tmp;
1674 variant_path = adium_info_dup_path_for_variant (info,
1675 adium_info_get_default_or_first_variant (info));
1677 /* Old custom templates had only 4 parameters.
1678 * New templates have 5 parameters */
1679 if (data->version <= 2 && data->custom_template) {
1680 tmp = string_with_format (template_html,
1683 "", /* The header */
1684 footer_html ? footer_html : "",
1687 tmp = string_with_format (template_html,
1689 data->version <= 2 ? "" : "@import url( \"main.css\" );",
1691 "", /* The header */
1692 footer_html ? footer_html : "",
1695 g_ptr_array_add (data->strings_to_free, tmp);
1696 data->template_html = tmp;
1698 g_free (template_html);
1699 g_free (footer_html);
1700 g_free (variant_path);
1706 empathy_adium_data_new (const gchar *path)
1708 EmpathyAdiumData *data;
1711 info = empathy_adium_info_new (path);
1712 data = empathy_adium_data_new_with_info (path, info);
1713 g_hash_table_unref (info);
1719 empathy_adium_data_ref (EmpathyAdiumData *data)
1721 g_return_val_if_fail (data != NULL, NULL);
1723 g_atomic_int_inc (&data->ref_count);
1729 empathy_adium_data_unref (EmpathyAdiumData *data)
1731 g_return_if_fail (data != NULL);
1733 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1734 g_free (data->path);
1735 g_free (data->basedir);
1736 g_free (data->default_avatar_filename);
1737 g_free (data->default_incoming_avatar_filename);
1738 g_free (data->default_outgoing_avatar_filename);
1739 g_hash_table_unref (data->info);
1740 g_ptr_array_unref (data->strings_to_free);
1742 g_slice_free (EmpathyAdiumData, data);
1747 empathy_adium_data_get_info (EmpathyAdiumData *data)
1749 g_return_val_if_fail (data != NULL, NULL);
1755 empathy_adium_data_get_path (EmpathyAdiumData *data)
1757 g_return_val_if_fail (data != NULL, NULL);