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 gboolean allow_scrolling;
68 } EmpathyThemeAdiumPriv;
70 struct _EmpathyAdiumData {
74 gchar *default_avatar_filename;
75 gchar *default_incoming_avatar_filename;
76 gchar *default_outgoing_avatar_filename;
83 gchar *in_content_html;
85 gchar *in_context_html;
87 gchar *in_nextcontent_html;
88 gsize in_nextcontent_len;
89 gchar *in_nextcontext_html;
90 gsize in_nextcontext_len;
91 gchar *out_content_html;
92 gsize out_content_len;
93 gchar *out_context_html;
94 gsize out_context_len;
95 gchar *out_nextcontent_html;
96 gsize out_nextcontent_len;
97 gchar *out_nextcontext_html;
98 gsize out_nextcontext_len;
103 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
110 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
111 WEBKIT_TYPE_WEB_VIEW,
112 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
113 theme_adium_iface_init));
116 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
118 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
119 WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
120 gboolean enable_webkit_developer_tools;
122 enable_webkit_developer_tools = g_settings_get_boolean (
123 priv->gsettings_chat,
124 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS);
126 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
127 "enable-developer-extras",
128 enable_webkit_developer_tools,
133 theme_adium_notify_enable_webkit_developer_tools_cb (GSettings *gsettings,
137 EmpathyThemeAdium *theme = user_data;
139 theme_adium_update_enable_webkit_developer_tools (theme);
143 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView *view,
144 WebKitWebFrame *web_frame,
145 WebKitNetworkRequest *request,
146 WebKitWebNavigationAction *action,
147 WebKitWebPolicyDecision *decision,
152 /* Only call url_show on clicks */
153 if (webkit_web_navigation_action_get_reason (action) !=
154 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
155 webkit_web_policy_decision_use (decision);
159 uri = webkit_network_request_get_uri (request);
160 empathy_url_show (GTK_WIDGET (view), uri);
162 webkit_web_policy_decision_ignore (decision);
167 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
170 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
172 GtkClipboard *clipboard;
174 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
176 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
177 gtk_clipboard_set_text (clipboard, uri, -1);
179 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
180 gtk_clipboard_set_text (clipboard, uri, -1);
186 theme_adium_open_address_cb (GtkMenuItem *menuitem,
189 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
192 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
194 empathy_url_show (GTK_WIDGET (menuitem), uri);
200 theme_adium_match_newline (const gchar *text,
202 EmpathyStringReplace replace_func,
203 EmpathyStringParser *sub_parsers,
206 GString *string = user_data;
214 /* Replace \n by <br/> */
215 for (i = 0; i < len && text[i] != '\0'; i++) {
216 if (text[i] == '\n') {
217 empathy_string_parser_substr (text + prev,
218 i - prev, sub_parsers,
220 g_string_append (string, "<br/>");
224 empathy_string_parser_substr (text + prev, i - prev,
225 sub_parsers, user_data);
229 theme_adium_replace_smiley (const gchar *text,
234 EmpathySmileyHit *hit = match_data;
235 GString *string = user_data;
237 /* Replace smiley by a <img/> tag */
238 g_string_append_printf (string,
239 "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
240 hit->path, (int)len, text, (int)len, text);
243 static EmpathyStringParser string_parsers[] = {
244 {empathy_string_match_link, empathy_string_replace_link},
245 {theme_adium_match_newline, NULL},
246 {empathy_string_match_all, empathy_string_replace_escaped},
250 static EmpathyStringParser string_parsers_with_smiley[] = {
251 {empathy_string_match_link, empathy_string_replace_link},
252 {empathy_string_match_smiley, theme_adium_replace_smiley},
253 {theme_adium_match_newline, NULL},
254 {empathy_string_match_all, empathy_string_replace_escaped},
259 theme_adium_parse_body (EmpathyThemeAdium *self,
262 EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
263 EmpathyStringParser *parsers;
266 /* Check if we have to parse smileys */
267 if (g_settings_get_boolean (priv->gsettings_chat,
268 EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
269 parsers = string_parsers_with_smiley;
271 parsers = string_parsers;
273 /* Parse text and construct string with links and smileys replaced
274 * by html tags. Also escape text to make sure html code is
275 * displayed verbatim. */
276 string = g_string_sized_new (strlen (text));
277 empathy_string_parser_substr (text, -1, parsers, string);
279 /* Wrap body in order to make tabs and multiple spaces displayed
280 * properly. See bug #625745. */
281 g_string_prepend (string, "<div style=\"display: inline; "
282 "white-space: pre-wrap\"'>");
283 g_string_append (string, "</div>");
285 return g_string_free (string, FALSE);
289 escape_and_append_len (GString *string, const gchar *str, gint len)
291 while (str != NULL && *str != '\0' && len != 0) {
295 g_string_append (string, "\\\\");
299 g_string_append (string, "\\\"");
302 /* Remove end of lines */
305 g_string_append_c (string, *str);
313 /* If *str starts with match, returns TRUE and move pointer to the end */
315 theme_adium_match (const gchar **str,
320 len = strlen (match);
321 if (strncmp (*str, match, len) == 0) {
329 /* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
331 theme_adium_match_with_format (const gchar **str,
335 const gchar *cur = *str;
338 if (!theme_adium_match (&cur, match)) {
343 end = strstr (cur, "}%");
348 *format = g_strndup (cur , end - cur);
353 /* List of colors used by %senderColor%. Copied from
354 * adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
356 static gchar *colors[] = {
357 "aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
358 "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
359 "darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
360 "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
361 "darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
362 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
363 "dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
364 "green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
365 "lightblue", "lightcoral",
366 "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
367 "lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
368 "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
369 "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
370 "mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
371 "olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
372 "palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
373 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
374 "sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
375 "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
380 theme_adium_append_html (EmpathyThemeAdium *theme,
382 const gchar *html, gsize len,
383 const gchar *message,
384 const gchar *avatar_filename,
386 const gchar *contact_id,
387 const gchar *service_name,
388 const gchar *message_classes,
393 const gchar *cur = NULL;
396 /* Make some search-and-replace in the html code */
397 string = g_string_sized_new (len + strlen (message));
398 g_string_append_printf (string, "%s(\"", func);
399 for (cur = html; *cur != '\0'; cur++) {
400 const gchar *replace = NULL;
401 gchar *dup_replace = NULL;
402 gchar *format = NULL;
404 /* Those are all well known keywords that needs replacement in
405 * html files. Please keep them in the same order than the adium
406 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
407 if (theme_adium_match (&cur, "%userIconPath%")) {
408 replace = avatar_filename;
409 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
410 replace = contact_id;
411 } else if (theme_adium_match (&cur, "%sender%")) {
413 } else if (theme_adium_match (&cur, "%senderColor%")) {
414 /* A color derived from the user's name.
415 * FIXME: If a colon separated list of HTML colors is at
416 * Incoming/SenderColors.txt it will be used instead of
417 * the default colors.
419 if (contact_id != NULL) {
420 guint hash = g_str_hash (contact_id);
421 replace = colors[hash % G_N_ELEMENTS (colors)];
423 } else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
424 /* FIXME: The path to the status icon of the sender
425 * (available, away, etc...)
427 } else if (theme_adium_match (&cur, "%messageDirection%")) {
428 /* FIXME: The text direction of the message
429 * (either rtl or ltr)
431 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
432 /* FIXME: The serverside (remotely set) name of the
433 * sender, such as an MSN display name.
435 * We don't have access to that yet so we use
436 * local alias instead.
439 } else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
440 /* FIXME: This keyword is used to represent the
441 * highlight background color. "X" is the opacity of the
442 * background, ranges from 0 to 1 and can be any decimal
445 } else if (theme_adium_match (&cur, "%message%")) {
447 } else if (theme_adium_match (&cur, "%time%") ||
448 theme_adium_match_with_format (&cur, "%time{", &format)) {
449 /* FIXME: format is not exactly strftime.
450 * See NSDateFormatter spec:
451 * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
454 dup_replace = empathy_time_to_string_local (timestamp,
455 format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
457 dup_replace = empathy_time_to_string_local (timestamp,
458 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
460 replace = dup_replace;
461 } else if (theme_adium_match (&cur, "%shortTime%")) {
462 dup_replace = empathy_time_to_string_local (timestamp,
463 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
464 replace = dup_replace;
465 } else if (theme_adium_match (&cur, "%service%")) {
466 replace = service_name;
467 } else if (theme_adium_match (&cur, "%variant%")) {
468 /* FIXME: The name of the active message style variant,
469 * with all spaces replaced with an underscore.
470 * A variant named "Alternating Messages - Blue Red"
471 * will become "Alternating_Messages_-_Blue_Red".
473 } else if (theme_adium_match (&cur, "%userIcons%")) {
474 /* FIXME: mus t be "hideIcons" if use preference is set
476 replace = "showIcons";
477 } else if (theme_adium_match (&cur, "%messageClasses%")) {
478 replace = message_classes;
479 } else if (theme_adium_match (&cur, "%status%")) {
480 /* FIXME: A description of the status event. This is
481 * neither in the user's local language nor expected to
482 * be displayed; it may be useful to use a different div
483 * class to present different types of status messages.
484 * The following is a list of some of the more important
485 * status messages; your message style should be able to
486 * handle being shown a status message not in this list,
487 * as even at present the list is incomplete and is
488 * certain to become out of date in the future:
497 * contact_joined (group chats)
501 * encryption (all OTR messages use this status)
502 * purple (all IRC topic and join/part messages use this status)
503 * fileTransferStarted
504 * fileTransferCompleted
507 escape_and_append_len (string, cur, 1);
511 /* Here we have a replacement to make */
512 escape_and_append_len (string, replace, -1);
514 g_free (dup_replace);
517 g_string_append (string, "\")");
519 script = g_string_free (string, FALSE);
520 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
525 theme_adium_append_event_escaped (EmpathyChatView *view,
526 const gchar *escaped)
528 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
529 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
533 html = priv->data->content_html;
534 len = priv->data->content_len;
536 /* Fallback to legacy status_html */
538 html = priv->data->status_html;
539 len = priv->data->status_len;
543 theme_adium_append_html (theme, "appendMessage",
544 html, len, escaped, NULL, NULL, NULL,
546 empathy_time_get_current (), FALSE);
548 DEBUG ("Couldn't find HTML file for this event");
551 /* There is no last contact */
552 if (priv->last_contact) {
553 g_object_unref (priv->last_contact);
554 priv->last_contact = NULL;
559 theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
561 WebKitDOMDocument *dom;
562 WebKitDOMNodeList *nodes;
564 GError *error = NULL;
566 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
571 /* Get all nodes with focus class */
572 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
574 DEBUG ("Error getting focus nodes: %s",
575 error ? error->message : "No error");
576 g_clear_error (&error);
580 /* Remove focus and firstFocus class */
581 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
582 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
583 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
585 gchar **classes, **iter;
586 GString *new_class_name;
587 gboolean first = TRUE;
589 if (element == NULL) {
593 class_name = webkit_dom_html_element_get_class_name (element);
594 classes = g_strsplit (class_name, " ", -1);
595 new_class_name = g_string_sized_new (strlen (class_name));
596 for (iter = classes; *iter != NULL; iter++) {
597 if (tp_strdiff (*iter, "focus") &&
598 tp_strdiff (*iter, "firstFocus")) {
600 g_string_append_c (new_class_name, ' ');
602 g_string_append (new_class_name, *iter);
607 webkit_dom_html_element_set_class_name (element, new_class_name->str);
610 g_strfreev (classes);
611 g_string_free (new_class_name, TRUE);
616 theme_adium_append_message (EmpathyChatView *view,
619 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
620 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
621 EmpathyContact *sender;
626 const gchar *contact_id;
627 EmpathyAvatar *avatar;
628 const gchar *avatar_filename = NULL;
633 const gchar *service_name;
634 GString *message_classes = NULL;
636 gboolean consecutive;
638 if (priv->pages_loading != 0) {
639 priv->message_queue = g_list_prepend (priv->message_queue,
644 /* Get information */
645 sender = empathy_message_get_sender (msg);
646 account = empathy_contact_get_account (sender);
647 service_name = empathy_protocol_name_to_display_name
648 (tp_account_get_protocol (account));
649 if (service_name == NULL)
650 service_name = tp_account_get_protocol (account);
651 timestamp = empathy_message_get_timestamp (msg);
652 body = empathy_message_get_body (msg);
653 body_escaped = theme_adium_parse_body (theme, body);
654 name = empathy_contact_get_alias (sender);
655 contact_id = empathy_contact_get_id (sender);
657 /* If this is a /me, append an event */
658 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
661 str = g_strdup_printf ("%s %s", name, body_escaped);
662 theme_adium_append_event_escaped (view, str);
665 g_free (body_escaped);
669 /* Get the avatar filename, or a fallback */
670 avatar = empathy_contact_get_avatar (sender);
672 avatar_filename = avatar->filename;
674 if (!avatar_filename) {
675 if (empathy_contact_is_user (sender)) {
676 avatar_filename = priv->data->default_outgoing_avatar_filename;
678 avatar_filename = priv->data->default_incoming_avatar_filename;
680 if (!avatar_filename) {
681 if (!priv->data->default_avatar_filename) {
682 priv->data->default_avatar_filename =
683 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
684 GTK_ICON_SIZE_DIALOG);
686 avatar_filename = priv->data->default_avatar_filename;
690 /* We want to join this message with the last one if
691 * - senders are the same contact,
692 * - last message was recieved recently,
693 * - last message and this message both are/aren't backlog, and
694 * - DisableCombineConsecutive is not set in theme's settings */
695 is_backlog = empathy_message_is_backlog (msg);
696 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
697 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
698 (is_backlog == priv->last_is_backlog) &&
699 !tp_asv_get_boolean (priv->data->info,
700 "DisableCombineConsecutive", NULL);
702 /* Define message classes */
703 message_classes = g_string_new ("message");
704 if (!priv->has_focus && !is_backlog) {
705 if (!priv->has_unread_message) {
706 /* This is the first message we receive since we lost
707 * focus; remove previous unread marks. */
708 theme_adium_remove_focus_marks (theme);
710 g_string_append (message_classes, " firstFocus");
711 priv->has_unread_message = TRUE;
713 g_string_append (message_classes, " focus");
716 g_string_append (message_classes, " history");
719 g_string_append (message_classes, " consecutive");
721 if (empathy_contact_is_user (sender)) {
722 g_string_append (message_classes, " outgoing");
724 g_string_append (message_classes, " incoming");
726 if (empathy_message_should_highlight (msg)) {
727 g_string_append (message_classes, " mention");
729 /* FIXME: other classes:
730 * autoreply - the message is an automatic response, generally due to an
732 * status - the message is a status change
733 * event - the message is a notification of something happening
734 * (for example, encryption being turned on)
735 * %status% - See %status% in theme_adium_append_html ()
738 /* Define javascript function to use */
740 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
742 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
745 html = priv->data->content_html;
746 len = priv->data->content_len;
748 /* Fallback to legacy Outgoing */
749 if (html == NULL && empathy_contact_is_user (sender)) {
752 html = priv->data->out_nextcontext_html;
753 len = priv->data->out_nextcontext_len;
756 /* Not backlog, or fallback if NextContext.html
759 html = priv->data->out_nextcontent_html;
760 len = priv->data->out_nextcontent_len;
764 /* Not consecutive, or fallback if NextContext.html and/or
765 * NextContent.html are missing */
768 html = priv->data->out_context_html;
769 len = priv->data->out_context_len;
773 html = priv->data->out_content_html;
774 len = priv->data->out_content_len;
779 /* Incoming, or fallback if outgoing files are missing */
783 html = priv->data->in_nextcontext_html;
784 len = priv->data->in_nextcontext_len;
787 /* Note backlog, or fallback if NextContext.html
790 html = priv->data->in_nextcontent_html;
791 len = priv->data->in_nextcontent_len;
795 /* Not consecutive, or fallback if NextContext.html and/or
796 * NextContent.html are missing */
799 html = priv->data->in_context_html;
800 len = priv->data->in_context_len;
804 html = priv->data->in_content_html;
805 len = priv->data->in_content_len;
811 theme_adium_append_html (theme, func, html, len, body_escaped,
812 avatar_filename, name, contact_id,
813 service_name, message_classes->str,
814 timestamp, is_backlog);
816 DEBUG ("Couldn't find HTML file for this message");
819 /* Keep the sender of the last displayed message */
820 if (priv->last_contact) {
821 g_object_unref (priv->last_contact);
823 priv->last_contact = g_object_ref (sender);
824 priv->last_timestamp = timestamp;
825 priv->last_is_backlog = is_backlog;
827 g_free (body_escaped);
828 g_string_free (message_classes, TRUE);
832 theme_adium_append_event (EmpathyChatView *view,
837 str_escaped = g_markup_escape_text (str, -1);
838 theme_adium_append_event_escaped (view, str_escaped);
839 g_free (str_escaped);
843 theme_adium_scroll (EmpathyChatView *view,
844 gboolean allow_scrolling)
846 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
848 priv->allow_scrolling = allow_scrolling;
849 if (allow_scrolling) {
850 empathy_chat_view_scroll_down (view);
855 theme_adium_scroll_down (EmpathyChatView *view)
857 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
861 theme_adium_get_has_selection (EmpathyChatView *view)
863 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
867 theme_adium_clear (EmpathyChatView *view)
869 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
872 priv->pages_loading++;
873 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
874 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
875 priv->data->template_html,
877 g_free (basedir_uri);
879 /* Clear last contact to avoid trying to add a 'joined'
880 * message when we don't have an insertion point. */
881 if (priv->last_contact) {
882 g_object_unref (priv->last_contact);
883 priv->last_contact = NULL;
888 theme_adium_find_previous (EmpathyChatView *view,
889 const gchar *search_criteria,
893 /* FIXME: Doesn't respect new_search */
894 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
895 search_criteria, match_case,
900 theme_adium_find_next (EmpathyChatView *view,
901 const gchar *search_criteria,
905 /* FIXME: Doesn't respect new_search */
906 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
907 search_criteria, match_case,
912 theme_adium_find_abilities (EmpathyChatView *view,
913 const gchar *search_criteria,
915 gboolean *can_do_previous,
916 gboolean *can_do_next)
918 /* FIXME: Does webkit provide an API for that? We have wrap=true in
919 * find_next and find_previous to work around this problem. */
921 *can_do_previous = TRUE;
927 theme_adium_highlight (EmpathyChatView *view,
931 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
932 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
933 text, match_case, 0);
934 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
939 theme_adium_copy_clipboard (EmpathyChatView *view)
941 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
945 theme_adium_focus_toggled (EmpathyChatView *view,
948 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
950 priv->has_focus = has_focus;
951 if (priv->has_focus) {
952 priv->has_unread_message = FALSE;
957 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
959 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
961 g_object_unref (hit_test_result);
965 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
967 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
968 WebKitHitTestResult *hit_test_result;
969 WebKitHitTestResultContext context;
973 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
974 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
977 menu = empathy_context_menu_new (GTK_WIDGET (view));
979 /* Select all item */
980 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
981 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
983 g_signal_connect_swapped (item, "activate",
984 G_CALLBACK (webkit_web_view_select_all),
988 if (webkit_web_view_can_copy_clipboard (view)) {
989 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
990 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
992 g_signal_connect_swapped (item, "activate",
993 G_CALLBACK (webkit_web_view_copy_clipboard),
997 /* Clear menu item */
998 item = gtk_separator_menu_item_new ();
999 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1001 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
1002 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1004 g_signal_connect_swapped (item, "activate",
1005 G_CALLBACK (empathy_chat_view_clear),
1008 /* We will only add the following menu items if we are
1009 * right-clicking a link */
1010 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1012 item = gtk_separator_menu_item_new ();
1013 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1015 /* Copy Link Address menu item */
1016 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1017 g_signal_connect (item, "activate",
1018 G_CALLBACK (theme_adium_copy_address_cb),
1020 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1022 /* Open Link menu item */
1023 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1024 g_signal_connect (item, "activate",
1025 G_CALLBACK (theme_adium_open_address_cb),
1027 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1030 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1031 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1034 /* Display the menu */
1035 gtk_widget_show_all (menu);
1036 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1037 event->button, event->time);
1041 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1043 if (event->button == 3) {
1044 gboolean developer_tools_enabled;
1046 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1047 "enable-developer-extras", &developer_tools_enabled, NULL);
1049 /* We currently have no way to add an inspector menu
1050 * item ourselves, so we disable our customized menu
1051 * if the developer extras are enabled. */
1052 if (!developer_tools_enabled) {
1053 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1058 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1062 theme_adium_iface_init (EmpathyChatViewIface *iface)
1064 iface->append_message = theme_adium_append_message;
1065 iface->append_event = theme_adium_append_event;
1066 iface->scroll = theme_adium_scroll;
1067 iface->scroll_down = theme_adium_scroll_down;
1068 iface->get_has_selection = theme_adium_get_has_selection;
1069 iface->clear = theme_adium_clear;
1070 iface->find_previous = theme_adium_find_previous;
1071 iface->find_next = theme_adium_find_next;
1072 iface->find_abilities = theme_adium_find_abilities;
1073 iface->highlight = theme_adium_highlight;
1074 iface->copy_clipboard = theme_adium_copy_clipboard;
1075 iface->focus_toggled = theme_adium_focus_toggled;
1079 theme_adium_load_finished_cb (WebKitWebView *view,
1080 WebKitWebFrame *frame,
1083 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1084 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1086 DEBUG ("Page loaded");
1087 priv->pages_loading--;
1089 if (priv->pages_loading != 0)
1092 /* Display queued messages */
1093 priv->message_queue = g_list_reverse (priv->message_queue);
1094 while (priv->message_queue) {
1095 EmpathyMessage *message = priv->message_queue->data;
1097 theme_adium_append_message (chat_view, message);
1098 priv->message_queue = g_list_remove (priv->message_queue, message);
1099 g_object_unref (message);
1104 theme_adium_finalize (GObject *object)
1106 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1108 empathy_adium_data_unref (priv->data);
1109 g_object_unref (priv->gsettings_chat);
1111 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1115 theme_adium_dispose (GObject *object)
1117 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1119 if (priv->smiley_manager) {
1120 g_object_unref (priv->smiley_manager);
1121 priv->smiley_manager = NULL;
1124 if (priv->last_contact) {
1125 g_object_unref (priv->last_contact);
1126 priv->last_contact = NULL;
1129 if (priv->inspector_window) {
1130 gtk_widget_destroy (priv->inspector_window);
1131 priv->inspector_window = NULL;
1134 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1138 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1139 EmpathyThemeAdium *theme)
1141 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1143 if (priv->inspector_window) {
1144 gtk_widget_show_all (priv->inspector_window);
1151 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1152 EmpathyThemeAdium *theme)
1154 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1156 if (priv->inspector_window) {
1157 gtk_widget_hide (priv->inspector_window);
1163 static WebKitWebView *
1164 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1165 WebKitWebView *web_view,
1166 EmpathyThemeAdium *theme)
1168 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1169 GtkWidget *scrolled_window;
1170 GtkWidget *inspector_web_view;
1172 if (!priv->inspector_window) {
1173 /* Create main window */
1174 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1175 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1177 g_signal_connect (priv->inspector_window, "delete-event",
1178 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1180 /* Pack a scrolled window */
1181 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1182 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1183 GTK_POLICY_AUTOMATIC,
1184 GTK_POLICY_AUTOMATIC);
1185 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1187 gtk_widget_show (scrolled_window);
1189 /* Pack a webview in the scrolled window. That webview will be
1190 * used to render the inspector tool. */
1191 inspector_web_view = webkit_web_view_new ();
1192 gtk_container_add (GTK_CONTAINER (scrolled_window),
1193 inspector_web_view);
1194 gtk_widget_show (scrolled_window);
1196 return WEBKIT_WEB_VIEW (inspector_web_view);
1202 static PangoFontDescription *
1203 theme_adium_get_default_font (void)
1205 GSettings *gsettings;
1206 PangoFontDescription *pango_fd;
1209 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1211 font_family = g_settings_get_string (gsettings,
1212 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1214 if (font_family == NULL)
1217 pango_fd = pango_font_description_from_string (font_family);
1218 g_free (font_family);
1219 g_object_unref (gsettings);
1224 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1228 g_object_set (w_settings, "default-font-family", name, NULL);
1229 g_object_set (w_settings, "default-font-size", size, NULL);
1233 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1235 PangoFontDescription *default_font_desc;
1236 GdkScreen *current_screen;
1238 gint pango_font_size = 0;
1240 default_font_desc = theme_adium_get_default_font ();
1241 if (default_font_desc == NULL)
1243 pango_font_size = pango_font_description_get_size (default_font_desc)
1245 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1246 current_screen = gdk_screen_get_default ();
1247 if (current_screen != NULL) {
1248 dpi = gdk_screen_get_resolution (current_screen);
1250 dpi = BORING_DPI_DEFAULT;
1252 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1254 theme_adium_set_webkit_font (w_settings,
1255 pango_font_description_get_family (default_font_desc),
1257 pango_font_description_free (default_font_desc);
1261 theme_adium_constructed (GObject *object)
1263 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1265 const gchar *font_family = NULL;
1267 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1268 WebKitWebSettings *webkit_settings;
1269 WebKitWebInspector *webkit_inspector;
1271 /* Set default settings */
1272 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1273 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1274 webkit_settings = webkit_web_view_get_settings (webkit_view);
1276 if (font_family && font_size) {
1277 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1279 theme_adium_set_default_font (webkit_settings);
1282 /* Setup webkit inspector */
1283 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1284 g_signal_connect (webkit_inspector, "inspect-web-view",
1285 G_CALLBACK (theme_adium_inspect_web_view_cb),
1287 g_signal_connect (webkit_inspector, "show-window",
1288 G_CALLBACK (theme_adium_inspector_show_window_cb),
1290 g_signal_connect (webkit_inspector, "close-window",
1291 G_CALLBACK (theme_adium_inspector_close_window_cb),
1295 priv->pages_loading = 1;
1297 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1298 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1299 priv->data->template_html,
1301 g_free (basedir_uri);
1305 theme_adium_get_property (GObject *object,
1310 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1313 case PROP_ADIUM_DATA:
1314 g_value_set_boxed (value, priv->data);
1317 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1323 theme_adium_set_property (GObject *object,
1325 const GValue *value,
1328 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1331 case PROP_ADIUM_DATA:
1332 g_assert (priv->data == NULL);
1333 priv->data = g_value_dup_boxed (value);
1336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1342 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1344 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1345 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1347 object_class->finalize = theme_adium_finalize;
1348 object_class->dispose = theme_adium_dispose;
1349 object_class->constructed = theme_adium_constructed;
1350 object_class->get_property = theme_adium_get_property;
1351 object_class->set_property = theme_adium_set_property;
1353 widget_class->button_press_event = theme_adium_button_press_event;
1355 g_object_class_install_property (object_class,
1357 g_param_spec_boxed ("adium-data",
1359 "Data for the adium theme",
1360 EMPATHY_TYPE_ADIUM_DATA,
1361 G_PARAM_CONSTRUCT_ONLY |
1363 G_PARAM_STATIC_STRINGS));
1365 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1369 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1371 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1372 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1376 priv->allow_scrolling = TRUE;
1377 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1379 g_signal_connect (theme, "load-finished",
1380 G_CALLBACK (theme_adium_load_finished_cb),
1382 g_signal_connect (theme, "navigation-policy-decision-requested",
1383 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1386 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1387 g_signal_connect (priv->gsettings_chat,
1388 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1389 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1392 theme_adium_update_enable_webkit_developer_tools (theme);
1396 empathy_theme_adium_new (EmpathyAdiumData *data)
1398 g_return_val_if_fail (data != NULL, NULL);
1400 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1406 empathy_adium_path_is_valid (const gchar *path)
1411 /* The theme is not valid if there is no Info.plist */
1412 file = g_build_filename (path, "Contents", "Info.plist",
1414 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1420 /* We ship a default Template.html as fallback if there is any problem
1421 * with the one inside the theme. The only other required file is
1423 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1425 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1431 /* Legacy themes have Incoming/Content.html (outgoing fallback to use
1433 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1434 "Content.html", NULL);
1435 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1442 empathy_adium_info_new (const gchar *path)
1446 GHashTable *info = NULL;
1448 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1450 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1451 value = empathy_plist_parse_from_file (file);
1457 info = g_value_dup_boxed (value);
1458 tp_g_value_slice_free (value);
1460 /* Insert the theme's path into the hash table,
1461 * keys have to be dupped */
1462 tp_asv_set_string (info, g_strdup ("path"), path);
1468 empathy_adium_data_get_type (void)
1470 static GType type_id = 0;
1474 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1475 (GBoxedCopyFunc) empathy_adium_data_ref,
1476 (GBoxedFreeFunc) empathy_adium_data_unref);
1483 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1485 EmpathyAdiumData *data;
1487 gchar *template_html = NULL;
1489 gchar *footer_html = NULL;
1492 gchar **strv = NULL;
1497 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1499 data = g_slice_new0 (EmpathyAdiumData);
1500 data->ref_count = 1;
1501 data->path = g_strdup (path);
1502 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1503 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1504 data->info = g_hash_table_ref (info);
1506 DEBUG ("Loading theme at %s", path);
1508 /* Load html files */
1509 file = g_build_filename (data->basedir, "Content.html", NULL);
1510 g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
1513 /* Fallback to legacy html files */
1514 if (data->content_html == NULL) {
1515 DEBUG (" fallback to legacy theme");
1517 file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1518 g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1521 file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1522 g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1525 file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1526 g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1529 file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1530 g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1533 file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1534 g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1537 file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1538 g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1541 file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1542 g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1545 file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1546 g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1549 file = g_build_filename (data->basedir, "Status.html", NULL);
1550 g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1554 file = g_build_filename (data->basedir, "Footer.html", NULL);
1555 g_file_get_contents (file, &footer_html, &footer_len, NULL);
1558 file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1559 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1560 data->default_incoming_avatar_filename = file;
1565 file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1566 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1567 data->default_outgoing_avatar_filename = file;
1572 css_path = g_build_filename (data->basedir, "main.css", NULL);
1574 /* There is 2 formats for Template.html: The old one has 4 parameters,
1575 * the new one has 5 parameters. */
1576 file = g_build_filename (data->basedir, "Template.html", NULL);
1577 if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1578 strv = g_strsplit (template_html, "%@", -1);
1579 len = g_strv_length (strv);
1583 if (len != 5 && len != 6) {
1584 /* Either the theme has no template or it don't have the good
1585 * number of parameters. Fallback to use our own template. */
1586 g_free (template_html);
1589 file = empathy_file_lookup ("Template.html", "data");
1590 g_file_get_contents (file, &template_html, &template_len, NULL);
1592 strv = g_strsplit (template_html, "%@", -1);
1593 len = g_strv_length (strv);
1596 /* Replace %@ with the needed information in the template html. */
1597 string = g_string_sized_new (template_len);
1598 g_string_append (string, strv[i++]);
1599 g_string_append (string, data->basedir);
1600 g_string_append (string, strv[i++]);
1602 const gchar *variant;
1604 /* We include main.css by default */
1605 g_string_append_printf (string, "@import url(\"%s\");", css_path);
1606 g_string_append (string, strv[i++]);
1607 variant = tp_asv_get_string (data->info, "DefaultVariant");
1609 g_string_append (string, "Variants/");
1610 g_string_append (string, variant);
1611 g_string_append (string, ".css");
1614 /* FIXME: We should set main.css OR the variant css */
1615 g_string_append (string, css_path);
1617 g_string_append (string, strv[i++]);
1618 g_string_append (string, ""); /* We don't want header */
1619 g_string_append (string, strv[i++]);
1620 /* FIXME: We should replace adium %macros% in footer */
1622 g_string_append (string, footer_html);
1624 g_string_append (string, strv[i++]);
1625 data->template_html = g_string_free (string, FALSE);
1627 g_free (footer_html);
1628 g_free (template_html);
1636 empathy_adium_data_new (const gchar *path)
1638 EmpathyAdiumData *data;
1641 info = empathy_adium_info_new (path);
1642 data = empathy_adium_data_new_with_info (path, info);
1643 g_hash_table_unref (info);
1649 empathy_adium_data_ref (EmpathyAdiumData *data)
1651 g_return_val_if_fail (data != NULL, NULL);
1653 g_atomic_int_inc (&data->ref_count);
1659 empathy_adium_data_unref (EmpathyAdiumData *data)
1661 g_return_if_fail (data != NULL);
1663 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1664 g_free (data->path);
1665 g_free (data->basedir);
1666 g_free (data->default_avatar_filename);
1667 g_free (data->default_incoming_avatar_filename);
1668 g_free (data->default_outgoing_avatar_filename);
1669 g_free (data->template_html);
1670 g_free (data->content_html);
1671 g_hash_table_unref (data->info);
1673 g_free (data->in_content_html);
1674 g_free (data->in_nextcontent_html);
1675 g_free (data->in_context_html);
1676 g_free (data->in_nextcontext_html);
1677 g_free (data->out_content_html);
1678 g_free (data->out_nextcontent_html);
1679 g_free (data->out_context_html);
1680 g_free (data->out_nextcontext_html);
1681 g_free (data->status_html);
1683 g_slice_free (EmpathyAdiumData, data);
1688 empathy_adium_data_get_info (EmpathyAdiumData *data)
1690 g_return_val_if_fail (data != NULL, NULL);
1696 empathy_adium_data_get_path (EmpathyAdiumData *data)
1698 g_return_val_if_fail (data != NULL, NULL);