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;
63 GtkWidget *inspector_window;
64 GSettings *gsettings_chat;
66 gboolean has_unread_message;
67 } EmpathyThemeAdiumPriv;
69 struct _EmpathyAdiumData {
73 gchar *default_avatar_filename;
74 gchar *default_incoming_avatar_filename;
75 gchar *default_outgoing_avatar_filename;
82 gchar *in_content_html;
84 gchar *in_context_html;
86 gchar *in_nextcontent_html;
87 gsize in_nextcontent_len;
88 gchar *in_nextcontext_html;
89 gsize in_nextcontext_len;
90 gchar *out_content_html;
91 gsize out_content_len;
92 gchar *out_context_html;
93 gsize out_context_len;
94 gchar *out_nextcontent_html;
95 gsize out_nextcontent_len;
96 gchar *out_nextcontext_html;
97 gsize out_nextcontext_len;
102 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
109 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
110 WEBKIT_TYPE_WEB_VIEW,
111 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
112 theme_adium_iface_init));
115 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
117 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
118 WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
119 gboolean enable_webkit_developer_tools;
121 enable_webkit_developer_tools = g_settings_get_boolean (
122 priv->gsettings_chat,
123 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS);
125 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
126 "enable-developer-extras",
127 enable_webkit_developer_tools,
132 theme_adium_notify_enable_webkit_developer_tools_cb (GSettings *gsettings,
136 EmpathyThemeAdium *theme = user_data;
138 theme_adium_update_enable_webkit_developer_tools (theme);
142 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView *view,
143 WebKitWebFrame *web_frame,
144 WebKitNetworkRequest *request,
145 WebKitWebNavigationAction *action,
146 WebKitWebPolicyDecision *decision,
151 /* Only call url_show on clicks */
152 if (webkit_web_navigation_action_get_reason (action) !=
153 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
154 webkit_web_policy_decision_use (decision);
158 uri = webkit_network_request_get_uri (request);
159 empathy_url_show (GTK_WIDGET (view), uri);
161 webkit_web_policy_decision_ignore (decision);
166 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
169 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
171 GtkClipboard *clipboard;
173 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
175 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
176 gtk_clipboard_set_text (clipboard, uri, -1);
178 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
179 gtk_clipboard_set_text (clipboard, uri, -1);
185 theme_adium_open_address_cb (GtkMenuItem *menuitem,
188 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
191 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
193 empathy_url_show (GTK_WIDGET (menuitem), uri);
199 theme_adium_match_newline (const gchar *text,
201 EmpathyStringReplace replace_func,
202 EmpathyStringParser *sub_parsers,
205 GString *string = user_data;
213 /* Replace \n by <br/> */
214 for (i = 0; i < len && text[i] != '\0'; i++) {
215 if (text[i] == '\n') {
216 empathy_string_parser_substr (text + prev,
217 i - prev, sub_parsers,
219 g_string_append (string, "<br/>");
223 empathy_string_parser_substr (text + prev, i - prev,
224 sub_parsers, user_data);
228 theme_adium_replace_smiley (const gchar *text,
233 EmpathySmileyHit *hit = match_data;
234 GString *string = user_data;
236 /* Replace smiley by a <img/> tag */
237 g_string_append_printf (string,
238 "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
239 hit->path, (int)len, text, (int)len, text);
242 static EmpathyStringParser string_parsers[] = {
243 {empathy_string_match_link, empathy_string_replace_link},
244 {theme_adium_match_newline, NULL},
245 {empathy_string_match_all, empathy_string_replace_escaped},
249 static EmpathyStringParser string_parsers_with_smiley[] = {
250 {empathy_string_match_link, empathy_string_replace_link},
251 {empathy_string_match_smiley, theme_adium_replace_smiley},
252 {theme_adium_match_newline, NULL},
253 {empathy_string_match_all, empathy_string_replace_escaped},
258 theme_adium_parse_body (EmpathyThemeAdium *self,
261 EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
262 EmpathyStringParser *parsers;
265 /* Check if we have to parse smileys */
266 if (g_settings_get_boolean (priv->gsettings_chat,
267 EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
268 parsers = string_parsers_with_smiley;
270 parsers = string_parsers;
272 /* Parse text and construct string with links and smileys replaced
273 * by html tags. Also escape text to make sure html code is
274 * displayed verbatim. */
275 string = g_string_sized_new (strlen (text));
276 empathy_string_parser_substr (text, -1, parsers, string);
278 /* Wrap body in order to make tabs and multiple spaces displayed
279 * properly. See bug #625745. */
280 g_string_prepend (string, "<div style=\"display: inline; "
281 "white-space: pre-wrap\"'>");
282 g_string_append (string, "</div>");
284 return g_string_free (string, FALSE);
288 escape_and_append_len (GString *string, const gchar *str, gint len)
290 while (str != NULL && *str != '\0' && len != 0) {
294 g_string_append (string, "\\\\");
298 g_string_append (string, "\\\"");
301 /* Remove end of lines */
304 g_string_append_c (string, *str);
312 /* If *str starts with match, returns TRUE and move pointer to the end */
314 theme_adium_match (const gchar **str,
319 len = strlen (match);
320 if (strncmp (*str, match, len) == 0) {
328 /* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
330 theme_adium_match_with_format (const gchar **str,
334 const gchar *cur = *str;
337 if (!theme_adium_match (&cur, match)) {
342 end = strstr (cur, "}%");
347 *format = g_strndup (cur , end - cur);
352 /* List of colors used by %senderColor%. Copied from
353 * adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
355 static gchar *colors[] = {
356 "aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
357 "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
358 "darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
359 "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
360 "darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
361 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
362 "dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
363 "green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
364 "lightblue", "lightcoral",
365 "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
366 "lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
367 "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
368 "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
369 "mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
370 "olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
371 "palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
372 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
373 "sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
374 "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
379 theme_adium_append_html (EmpathyThemeAdium *theme,
381 const gchar *html, gsize len,
382 const gchar *message,
383 const gchar *avatar_filename,
385 const gchar *contact_id,
386 const gchar *service_name,
387 const gchar *message_classes,
392 const gchar *cur = NULL;
395 /* Make some search-and-replace in the html code */
396 string = g_string_sized_new (len + strlen (message));
397 g_string_append_printf (string, "%s(\"", func);
398 for (cur = html; *cur != '\0'; cur++) {
399 const gchar *replace = NULL;
400 gchar *dup_replace = NULL;
401 gchar *format = NULL;
403 /* Those are all well known keywords that needs replacement in
404 * html files. Please keep them in the same order than the adium
405 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
406 if (theme_adium_match (&cur, "%userIconPath%")) {
407 replace = avatar_filename;
408 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
409 replace = contact_id;
410 } else if (theme_adium_match (&cur, "%sender%")) {
412 } else if (theme_adium_match (&cur, "%senderColor%")) {
413 /* A color derived from the user's name.
414 * FIXME: If a colon separated list of HTML colors is at
415 * Incoming/SenderColors.txt it will be used instead of
416 * the default colors.
418 guint hash = g_str_hash (contact_id);
420 replace = colors[hash % G_N_ELEMENTS (colors)];
421 } else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
422 /* FIXME: The path to the status icon of the sender
423 * (available, away, etc...)
425 } else if (theme_adium_match (&cur, "%messageDirection%")) {
426 /* FIXME: The text direction of the message
427 * (either rtl or ltr)
429 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
430 /* FIXME: The serverside (remotely set) name of the
431 * sender, such as an MSN display name.
433 * We don't have access to that yet so we use
434 * local alias instead.
437 } else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
438 /* FIXME: This keyword is used to represent the
439 * highlight background color. "X" is the opacity of the
440 * background, ranges from 0 to 1 and can be any decimal
443 } else if (theme_adium_match (&cur, "%message%")) {
445 } else if (theme_adium_match (&cur, "%time%") ||
446 theme_adium_match_with_format (&cur, "%time{", &format)) {
447 /* FIXME: format is not exactly strftime.
448 * See NSDateFormatter spec:
449 * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
452 dup_replace = empathy_time_to_string_local (timestamp,
453 format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
455 dup_replace = empathy_time_to_string_local (timestamp,
456 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
458 replace = dup_replace;
459 } else if (theme_adium_match (&cur, "%shortTime%")) {
460 dup_replace = empathy_time_to_string_local (timestamp,
461 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
462 replace = dup_replace;
463 } else if (theme_adium_match (&cur, "%service%")) {
464 replace = service_name;
465 } else if (theme_adium_match (&cur, "%variant%")) {
466 /* FIXME: The name of the active message style variant,
467 * with all spaces replaced with an underscore.
468 * A variant named "Alternating Messages - Blue Red"
469 * will become "Alternating_Messages_-_Blue_Red".
471 } else if (theme_adium_match (&cur, "%userIcons%")) {
472 /* FIXME: mus t be "hideIcons" if use preference is set
474 replace = "showIcons";
475 } else if (theme_adium_match (&cur, "%messageClasses%")) {
476 replace = message_classes;
477 } else if (theme_adium_match (&cur, "%status%")) {
478 /* FIXME: A description of the status event. This is
479 * neither in the user's local language nor expected to
480 * be displayed; it may be useful to use a different div
481 * class to present different types of status messages.
482 * The following is a list of some of the more important
483 * status messages; your message style should be able to
484 * handle being shown a status message not in this list,
485 * as even at present the list is incomplete and is
486 * certain to become out of date in the future:
495 * contact_joined (group chats)
499 * encryption (all OTR messages use this status)
500 * purple (all IRC topic and join/part messages use this status)
501 * fileTransferStarted
502 * fileTransferCompleted
505 escape_and_append_len (string, cur, 1);
509 /* Here we have a replacement to make */
510 escape_and_append_len (string, replace, -1);
512 g_free (dup_replace);
515 g_string_append (string, "\")");
517 script = g_string_free (string, FALSE);
518 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
523 theme_adium_append_event_escaped (EmpathyChatView *view,
524 const gchar *escaped)
526 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
527 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
531 html = priv->data->content_html;
532 len = priv->data->content_len;
534 /* Fallback to legacy status_html */
536 html = priv->data->status_html;
537 len = priv->data->status_len;
541 theme_adium_append_html (theme, "appendMessage",
542 html, len, escaped, NULL, NULL, NULL,
544 empathy_time_get_current (), FALSE);
546 DEBUG ("Couldn't find HTML file for this event");
549 /* There is no last contact */
550 if (priv->last_contact) {
551 g_object_unref (priv->last_contact);
552 priv->last_contact = NULL;
557 theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
559 WebKitDOMDocument *dom;
560 WebKitDOMNodeList *nodes;
562 GError *error = NULL;
564 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
569 /* Get all nodes with focus class */
570 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
572 DEBUG ("Error getting focus nodes: %s",
573 error ? error->message : "No error");
574 g_clear_error (&error);
578 /* Remove focus and firstFocus class */
579 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
580 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
581 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
583 gchar **classes, **iter;
584 GString *new_class_name;
585 gboolean first = TRUE;
587 if (element == NULL) {
591 class_name = webkit_dom_html_element_get_class_name (element);
592 classes = g_strsplit (class_name, " ", -1);
593 new_class_name = g_string_sized_new (strlen (class_name));
594 for (iter = classes; *iter != NULL; iter++) {
595 if (tp_strdiff (*iter, "focus") &&
596 tp_strdiff (*iter, "firstFocus")) {
598 g_string_append_c (new_class_name, ' ');
600 g_string_append (new_class_name, *iter);
605 webkit_dom_html_element_set_class_name (element, new_class_name->str);
608 g_strfreev (classes);
609 g_string_free (new_class_name, TRUE);
614 theme_adium_append_message (EmpathyChatView *view,
617 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
618 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
619 EmpathyContact *sender;
624 const gchar *contact_id;
625 EmpathyAvatar *avatar;
626 const gchar *avatar_filename = NULL;
631 const gchar *service_name;
632 GString *message_classes = NULL;
634 gboolean consecutive;
636 if (priv->pages_loading != 0) {
637 priv->message_queue = g_list_prepend (priv->message_queue,
642 /* Get information */
643 sender = empathy_message_get_sender (msg);
644 account = empathy_contact_get_account (sender);
645 service_name = empathy_protocol_name_to_display_name
646 (tp_account_get_protocol (account));
647 if (service_name == NULL)
648 service_name = tp_account_get_protocol (account);
649 timestamp = empathy_message_get_timestamp (msg);
650 body = empathy_message_get_body (msg);
651 body_escaped = theme_adium_parse_body (theme, body);
652 name = empathy_contact_get_alias (sender);
653 contact_id = empathy_contact_get_id (sender);
655 /* If this is a /me, append an event */
656 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
659 str = g_strdup_printf ("%s %s", name, body_escaped);
660 theme_adium_append_event_escaped (view, str);
663 g_free (body_escaped);
667 /* Get the avatar filename, or a fallback */
668 avatar = empathy_contact_get_avatar (sender);
670 avatar_filename = avatar->filename;
672 if (!avatar_filename) {
673 if (empathy_contact_is_user (sender)) {
674 avatar_filename = priv->data->default_outgoing_avatar_filename;
676 avatar_filename = priv->data->default_incoming_avatar_filename;
678 if (!avatar_filename) {
679 if (!priv->data->default_avatar_filename) {
680 priv->data->default_avatar_filename =
681 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
682 GTK_ICON_SIZE_DIALOG);
684 avatar_filename = priv->data->default_avatar_filename;
688 /* We want to join this message with the last one if
689 * - senders are the same contact,
690 * - last message was recieved recently,
691 * - last message and this message both are/aren't backlog, and
692 * - DisableCombineConsecutive is not set in theme's settings */
693 is_backlog = empathy_message_is_backlog (msg);
694 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
695 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
696 (is_backlog == priv->last_is_backlog) &&
697 !tp_asv_get_boolean (priv->data->info,
698 "DisableCombineConsecutive", NULL);
700 /* Define message classes */
701 message_classes = g_string_new ("message");
702 if (!priv->has_focus && !is_backlog) {
703 if (!priv->has_unread_message) {
704 /* This is the first message we receive since we lost
705 * focus; remove previous unread marks. */
706 theme_adium_remove_focus_marks (theme);
708 g_string_append (message_classes, " firstFocus");
709 priv->has_unread_message = TRUE;
711 g_string_append (message_classes, " focus");
714 g_string_append (message_classes, " history");
717 g_string_append (message_classes, " consecutive");
719 if (empathy_contact_is_user (sender)) {
720 g_string_append (message_classes, " outgoing");
722 g_string_append (message_classes, " incoming");
724 /* FIXME: other classes:
725 * autoreply - the message is an automatic response, generally due to an
727 * mention - the incoming message (in groupchat) matches your username
728 * or one of the mention keywords specified in Adium's
730 * status - the message is a status change
731 * event - the message is a notification of something happening
732 * (for example, encryption being turned on)
733 * %status% - See %status% in theme_adium_append_html()
736 /* Define javascript function to use */
738 func = "appendNextMessage";
740 func = "appendMessage";
743 html = priv->data->content_html;
744 len = priv->data->content_len;
746 /* Fallback to legacy Outgoing */
747 if (html == NULL && empathy_contact_is_user (sender)) {
750 html = priv->data->out_nextcontext_html;
751 len = priv->data->out_nextcontext_len;
754 /* Not backlog, or fallback if NextContext.html
757 html = priv->data->out_nextcontent_html;
758 len = priv->data->out_nextcontent_len;
762 /* Not consecutive, or fallback if NextContext.html and/or
763 * NextContent.html are missing */
766 html = priv->data->out_context_html;
767 len = priv->data->out_context_len;
771 html = priv->data->out_content_html;
772 len = priv->data->out_content_len;
777 /* Incoming, or fallback if outgoing files are missing */
781 html = priv->data->in_nextcontext_html;
782 len = priv->data->in_nextcontext_len;
785 /* Note backlog, or fallback if NextContext.html
788 html = priv->data->in_nextcontent_html;
789 len = priv->data->in_nextcontent_len;
793 /* Not consecutive, or fallback if NextContext.html and/or
794 * NextContent.html are missing */
797 html = priv->data->in_context_html;
798 len = priv->data->in_context_len;
802 html = priv->data->in_content_html;
803 len = priv->data->in_content_len;
809 theme_adium_append_html (theme, func, html, len, body_escaped,
810 avatar_filename, name, contact_id,
811 service_name, message_classes->str,
812 timestamp, is_backlog);
814 DEBUG ("Couldn't find HTML file for this message");
817 /* Keep the sender of the last displayed message */
818 if (priv->last_contact) {
819 g_object_unref (priv->last_contact);
821 priv->last_contact = g_object_ref (sender);
822 priv->last_timestamp = timestamp;
823 priv->last_is_backlog = is_backlog;
825 g_free (body_escaped);
826 g_string_free (message_classes, TRUE);
830 theme_adium_append_event (EmpathyChatView *view,
835 str_escaped = g_markup_escape_text (str, -1);
836 theme_adium_append_event_escaped (view, str_escaped);
837 g_free (str_escaped);
841 theme_adium_scroll (EmpathyChatView *view,
842 gboolean allow_scrolling)
844 /* FIXME: Is it possible? I guess we need a js function, but I don't
849 theme_adium_scroll_down (EmpathyChatView *view)
851 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "scrollToBottom()");
855 theme_adium_get_has_selection (EmpathyChatView *view)
857 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
861 theme_adium_clear (EmpathyChatView *view)
863 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
866 priv->pages_loading++;
867 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
868 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
869 priv->data->template_html,
871 g_free (basedir_uri);
873 /* Clear last contact to avoid trying to add a 'joined'
874 * message when we don't have an insertion point. */
875 if (priv->last_contact) {
876 g_object_unref (priv->last_contact);
877 priv->last_contact = NULL;
882 theme_adium_find_previous (EmpathyChatView *view,
883 const gchar *search_criteria,
887 /* FIXME: Doesn't respect new_search */
888 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
889 search_criteria, match_case,
894 theme_adium_find_next (EmpathyChatView *view,
895 const gchar *search_criteria,
899 /* FIXME: Doesn't respect new_search */
900 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
901 search_criteria, match_case,
906 theme_adium_find_abilities (EmpathyChatView *view,
907 const gchar *search_criteria,
909 gboolean *can_do_previous,
910 gboolean *can_do_next)
912 /* FIXME: Does webkit provide an API for that? We have wrap=true in
913 * find_next and find_previous to work around this problem. */
915 *can_do_previous = TRUE;
921 theme_adium_highlight (EmpathyChatView *view,
925 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
926 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
927 text, match_case, 0);
928 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
933 theme_adium_copy_clipboard (EmpathyChatView *view)
935 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
939 theme_adium_focus_toggled (EmpathyChatView *view,
942 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
944 priv->has_focus = has_focus;
945 if (priv->has_focus) {
946 priv->has_unread_message = FALSE;
951 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
953 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
955 g_object_unref (hit_test_result);
959 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
961 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
962 WebKitHitTestResult *hit_test_result;
963 WebKitHitTestResultContext context;
967 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
968 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
971 menu = empathy_context_menu_new (GTK_WIDGET (view));
973 /* Select all item */
974 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
975 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
977 g_signal_connect_swapped (item, "activate",
978 G_CALLBACK (webkit_web_view_select_all),
982 if (webkit_web_view_can_copy_clipboard (view)) {
983 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
984 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
986 g_signal_connect_swapped (item, "activate",
987 G_CALLBACK (webkit_web_view_copy_clipboard),
991 /* Clear menu item */
992 item = gtk_separator_menu_item_new ();
993 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
995 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
996 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
998 g_signal_connect_swapped (item, "activate",
999 G_CALLBACK (empathy_chat_view_clear),
1002 /* We will only add the following menu items if we are
1003 * right-clicking a link */
1004 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1006 item = gtk_separator_menu_item_new ();
1007 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1009 /* Copy Link Address menu item */
1010 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1011 g_signal_connect (item, "activate",
1012 G_CALLBACK (theme_adium_copy_address_cb),
1014 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1016 /* Open Link menu item */
1017 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1018 g_signal_connect (item, "activate",
1019 G_CALLBACK (theme_adium_open_address_cb),
1021 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1024 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1025 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1028 /* Display the menu */
1029 gtk_widget_show_all (menu);
1030 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1031 event->button, event->time);
1035 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1037 if (event->button == 3) {
1038 gboolean developer_tools_enabled;
1040 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1041 "enable-developer-extras", &developer_tools_enabled, NULL);
1043 /* We currently have no way to add an inspector menu
1044 * item ourselves, so we disable our customized menu
1045 * if the developer extras are enabled. */
1046 if (!developer_tools_enabled) {
1047 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1052 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1056 theme_adium_iface_init (EmpathyChatViewIface *iface)
1058 iface->append_message = theme_adium_append_message;
1059 iface->append_event = theme_adium_append_event;
1060 iface->scroll = theme_adium_scroll;
1061 iface->scroll_down = theme_adium_scroll_down;
1062 iface->get_has_selection = theme_adium_get_has_selection;
1063 iface->clear = theme_adium_clear;
1064 iface->find_previous = theme_adium_find_previous;
1065 iface->find_next = theme_adium_find_next;
1066 iface->find_abilities = theme_adium_find_abilities;
1067 iface->highlight = theme_adium_highlight;
1068 iface->copy_clipboard = theme_adium_copy_clipboard;
1069 iface->focus_toggled = theme_adium_focus_toggled;
1073 theme_adium_load_finished_cb (WebKitWebView *view,
1074 WebKitWebFrame *frame,
1077 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1078 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1080 DEBUG ("Page loaded");
1081 priv->pages_loading--;
1083 if (priv->pages_loading != 0)
1086 /* Display queued messages */
1087 priv->message_queue = g_list_reverse (priv->message_queue);
1088 while (priv->message_queue) {
1089 EmpathyMessage *message = priv->message_queue->data;
1091 theme_adium_append_message (chat_view, message);
1092 priv->message_queue = g_list_remove (priv->message_queue, message);
1093 g_object_unref (message);
1098 theme_adium_finalize (GObject *object)
1100 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1102 empathy_adium_data_unref (priv->data);
1103 g_object_unref (priv->gsettings_chat);
1105 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1109 theme_adium_dispose (GObject *object)
1111 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1113 if (priv->smiley_manager) {
1114 g_object_unref (priv->smiley_manager);
1115 priv->smiley_manager = NULL;
1118 if (priv->last_contact) {
1119 g_object_unref (priv->last_contact);
1120 priv->last_contact = NULL;
1123 if (priv->inspector_window) {
1124 gtk_widget_destroy (priv->inspector_window);
1125 priv->inspector_window = NULL;
1128 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1132 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1133 EmpathyThemeAdium *theme)
1135 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1137 if (priv->inspector_window) {
1138 gtk_widget_show_all (priv->inspector_window);
1145 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1146 EmpathyThemeAdium *theme)
1148 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1150 if (priv->inspector_window) {
1151 gtk_widget_hide (priv->inspector_window);
1157 static WebKitWebView *
1158 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1159 WebKitWebView *web_view,
1160 EmpathyThemeAdium *theme)
1162 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1163 GtkWidget *scrolled_window;
1164 GtkWidget *inspector_web_view;
1166 if (!priv->inspector_window) {
1167 /* Create main window */
1168 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1169 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1171 g_signal_connect (priv->inspector_window, "delete-event",
1172 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1174 /* Pack a scrolled window */
1175 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1176 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1177 GTK_POLICY_AUTOMATIC,
1178 GTK_POLICY_AUTOMATIC);
1179 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1181 gtk_widget_show (scrolled_window);
1183 /* Pack a webview in the scrolled window. That webview will be
1184 * used to render the inspector tool. */
1185 inspector_web_view = webkit_web_view_new ();
1186 gtk_container_add (GTK_CONTAINER (scrolled_window),
1187 inspector_web_view);
1188 gtk_widget_show (scrolled_window);
1190 return WEBKIT_WEB_VIEW (inspector_web_view);
1196 static PangoFontDescription *
1197 theme_adium_get_default_font (void)
1199 GSettings *gsettings;
1200 PangoFontDescription *pango_fd;
1203 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1205 font_family = g_settings_get_string (gsettings,
1206 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1208 if (font_family == NULL)
1211 pango_fd = pango_font_description_from_string (font_family);
1212 g_free (font_family);
1213 g_object_unref (gsettings);
1218 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1222 g_object_set (w_settings, "default-font-family", name, NULL);
1223 g_object_set (w_settings, "default-font-size", size, NULL);
1227 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1229 PangoFontDescription *default_font_desc;
1230 GdkScreen *current_screen;
1232 gint pango_font_size = 0;
1234 default_font_desc = theme_adium_get_default_font ();
1235 if (default_font_desc == NULL)
1237 pango_font_size = pango_font_description_get_size (default_font_desc)
1239 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1240 current_screen = gdk_screen_get_default ();
1241 if (current_screen != NULL) {
1242 dpi = gdk_screen_get_resolution (current_screen);
1244 dpi = BORING_DPI_DEFAULT;
1246 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1248 theme_adium_set_webkit_font (w_settings,
1249 pango_font_description_get_family (default_font_desc),
1251 pango_font_description_free (default_font_desc);
1255 theme_adium_constructed (GObject *object)
1257 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1259 const gchar *font_family = NULL;
1261 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1262 WebKitWebSettings *webkit_settings;
1263 WebKitWebInspector *webkit_inspector;
1265 /* Set default settings */
1266 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1267 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1268 webkit_settings = webkit_web_view_get_settings (webkit_view);
1270 if (font_family && font_size) {
1271 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1273 theme_adium_set_default_font (webkit_settings);
1276 /* Setup webkit inspector */
1277 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1278 g_signal_connect (webkit_inspector, "inspect-web-view",
1279 G_CALLBACK (theme_adium_inspect_web_view_cb),
1281 g_signal_connect (webkit_inspector, "show-window",
1282 G_CALLBACK (theme_adium_inspector_show_window_cb),
1284 g_signal_connect (webkit_inspector, "close-window",
1285 G_CALLBACK (theme_adium_inspector_close_window_cb),
1289 priv->pages_loading = 1;
1291 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1292 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1293 priv->data->template_html,
1295 g_free (basedir_uri);
1299 theme_adium_get_property (GObject *object,
1304 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1307 case PROP_ADIUM_DATA:
1308 g_value_set_boxed (value, priv->data);
1311 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1317 theme_adium_set_property (GObject *object,
1319 const GValue *value,
1322 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1325 case PROP_ADIUM_DATA:
1326 g_assert (priv->data == NULL);
1327 priv->data = g_value_dup_boxed (value);
1330 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1336 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1338 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1339 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1341 object_class->finalize = theme_adium_finalize;
1342 object_class->dispose = theme_adium_dispose;
1343 object_class->constructed = theme_adium_constructed;
1344 object_class->get_property = theme_adium_get_property;
1345 object_class->set_property = theme_adium_set_property;
1347 widget_class->button_press_event = theme_adium_button_press_event;
1349 g_object_class_install_property (object_class,
1351 g_param_spec_boxed ("adium-data",
1353 "Data for the adium theme",
1354 EMPATHY_TYPE_ADIUM_DATA,
1355 G_PARAM_CONSTRUCT_ONLY |
1357 G_PARAM_STATIC_STRINGS));
1359 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1363 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1365 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1366 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1370 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1372 g_signal_connect (theme, "load-finished",
1373 G_CALLBACK (theme_adium_load_finished_cb),
1375 g_signal_connect (theme, "navigation-policy-decision-requested",
1376 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1379 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1380 g_signal_connect (priv->gsettings_chat,
1381 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1382 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1385 theme_adium_update_enable_webkit_developer_tools (theme);
1389 empathy_theme_adium_new (EmpathyAdiumData *data)
1391 g_return_val_if_fail (data != NULL, NULL);
1393 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1399 empathy_adium_path_is_valid (const gchar *path)
1404 /* The theme is not valid if there is no Info.plist */
1405 file = g_build_filename (path, "Contents", "Info.plist",
1407 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1413 /* We ship a default Template.html as fallback if there is any problem
1414 * with the one inside the theme. The only other required file is
1416 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1418 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1424 /* Legacy themes have Incoming/Content.html (outgoing fallback to use
1426 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1427 "Content.html", NULL);
1428 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1435 empathy_adium_info_new (const gchar *path)
1439 GHashTable *info = NULL;
1441 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1443 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1444 value = empathy_plist_parse_from_file (file);
1450 info = g_value_dup_boxed (value);
1451 tp_g_value_slice_free (value);
1453 /* Insert the theme's path into the hash table,
1454 * keys have to be dupped */
1455 tp_asv_set_string (info, g_strdup ("path"), path);
1461 empathy_adium_data_get_type (void)
1463 static GType type_id = 0;
1467 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1468 (GBoxedCopyFunc) empathy_adium_data_ref,
1469 (GBoxedFreeFunc) empathy_adium_data_unref);
1476 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1478 EmpathyAdiumData *data;
1480 gchar *template_html = NULL;
1482 gchar *footer_html = NULL;
1485 gchar **strv = NULL;
1490 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1492 data = g_slice_new0 (EmpathyAdiumData);
1493 data->ref_count = 1;
1494 data->path = g_strdup (path);
1495 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1496 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1497 data->info = g_hash_table_ref (info);
1499 DEBUG ("Loading theme at %s", path);
1501 /* Load html files */
1502 file = g_build_filename (data->basedir, "Content.html", NULL);
1503 g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
1506 /* Fallback to legacy html files */
1507 if (data->content_html == NULL) {
1508 DEBUG (" fallback to legacy theme");
1510 file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1511 g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1514 file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1515 g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1518 file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1519 g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1522 file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1523 g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1526 file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1527 g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1530 file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1531 g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1534 file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1535 g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1538 file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1539 g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1542 file = g_build_filename (data->basedir, "Status.html", NULL);
1543 g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1547 file = g_build_filename (data->basedir, "Footer.html", NULL);
1548 g_file_get_contents (file, &footer_html, &footer_len, NULL);
1551 file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1552 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1553 data->default_incoming_avatar_filename = file;
1558 file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1559 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1560 data->default_outgoing_avatar_filename = file;
1565 css_path = g_build_filename (data->basedir, "main.css", NULL);
1567 /* There is 2 formats for Template.html: The old one has 4 parameters,
1568 * the new one has 5 parameters. */
1569 file = g_build_filename (data->basedir, "Template.html", NULL);
1570 if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1571 strv = g_strsplit (template_html, "%@", -1);
1572 len = g_strv_length (strv);
1576 if (len != 5 && len != 6) {
1577 /* Either the theme has no template or it don't have the good
1578 * number of parameters. Fallback to use our own template. */
1579 g_free (template_html);
1582 file = empathy_file_lookup ("Template.html", "data");
1583 g_file_get_contents (file, &template_html, &template_len, NULL);
1585 strv = g_strsplit (template_html, "%@", -1);
1586 len = g_strv_length (strv);
1589 /* Replace %@ with the needed information in the template html. */
1590 string = g_string_sized_new (template_len);
1591 g_string_append (string, strv[i++]);
1592 g_string_append (string, data->basedir);
1593 g_string_append (string, strv[i++]);
1595 const gchar *variant;
1597 /* We include main.css by default */
1598 g_string_append_printf (string, "@import url(\"%s\");", css_path);
1599 g_string_append (string, strv[i++]);
1600 variant = tp_asv_get_string (data->info, "DefaultVariant");
1602 g_string_append (string, "Variants/");
1603 g_string_append (string, variant);
1604 g_string_append (string, ".css");
1607 /* FIXME: We should set main.css OR the variant css */
1608 g_string_append (string, css_path);
1610 g_string_append (string, strv[i++]);
1611 g_string_append (string, ""); /* We don't want header */
1612 g_string_append (string, strv[i++]);
1613 /* FIXME: We should replace adium %macros% in footer */
1615 g_string_append (string, footer_html);
1617 g_string_append (string, strv[i++]);
1618 data->template_html = g_string_free (string, FALSE);
1620 g_free (footer_html);
1621 g_free (template_html);
1629 empathy_adium_data_new (const gchar *path)
1631 EmpathyAdiumData *data;
1634 info = empathy_adium_info_new (path);
1635 data = empathy_adium_data_new_with_info (path, info);
1636 g_hash_table_unref (info);
1642 empathy_adium_data_ref (EmpathyAdiumData *data)
1644 g_return_val_if_fail (data != NULL, NULL);
1646 g_atomic_int_inc (&data->ref_count);
1652 empathy_adium_data_unref (EmpathyAdiumData *data)
1654 g_return_if_fail (data != NULL);
1656 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1657 g_free (data->path);
1658 g_free (data->basedir);
1659 g_free (data->default_avatar_filename);
1660 g_free (data->default_incoming_avatar_filename);
1661 g_free (data->default_outgoing_avatar_filename);
1662 g_free (data->template_html);
1663 g_free (data->content_html);
1664 g_hash_table_unref (data->info);
1666 g_free (data->in_content_html);
1667 g_free (data->in_nextcontent_html);
1668 g_free (data->in_context_html);
1669 g_free (data->in_nextcontext_html);
1670 g_free (data->out_content_html);
1671 g_free (data->out_nextcontent_html);
1672 g_free (data->out_context_html);
1673 g_free (data->out_nextcontext_html);
1674 g_free (data->status_html);
1676 g_slice_free (EmpathyAdiumData, data);
1681 empathy_adium_data_get_info (EmpathyAdiumData *data)
1683 g_return_val_if_fail (data != NULL, NULL);
1689 empathy_adium_data_get_path (EmpathyAdiumData *data)
1691 g_return_val_if_fail (data != NULL, NULL);