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 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
730 g_string_append (message_classes, " autoreply");
732 /* FIXME: other classes:
733 * status - the message is a status change
734 * event - the message is a notification of something happening
735 * (for example, encryption being turned on)
736 * %status% - See %status% in theme_adium_append_html ()
739 /* Define javascript function to use */
741 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
743 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
746 html = priv->data->content_html;
747 len = priv->data->content_len;
749 /* Fallback to legacy Outgoing */
750 if (html == NULL && empathy_contact_is_user (sender)) {
753 html = priv->data->out_nextcontext_html;
754 len = priv->data->out_nextcontext_len;
757 /* Not backlog, or fallback if NextContext.html
760 html = priv->data->out_nextcontent_html;
761 len = priv->data->out_nextcontent_len;
765 /* Not consecutive, or fallback if NextContext.html and/or
766 * NextContent.html are missing */
769 html = priv->data->out_context_html;
770 len = priv->data->out_context_len;
774 html = priv->data->out_content_html;
775 len = priv->data->out_content_len;
780 /* Incoming, or fallback if outgoing files are missing */
784 html = priv->data->in_nextcontext_html;
785 len = priv->data->in_nextcontext_len;
788 /* Note backlog, or fallback if NextContext.html
791 html = priv->data->in_nextcontent_html;
792 len = priv->data->in_nextcontent_len;
796 /* Not consecutive, or fallback if NextContext.html and/or
797 * NextContent.html are missing */
800 html = priv->data->in_context_html;
801 len = priv->data->in_context_len;
805 html = priv->data->in_content_html;
806 len = priv->data->in_content_len;
812 theme_adium_append_html (theme, func, html, len, body_escaped,
813 avatar_filename, name, contact_id,
814 service_name, message_classes->str,
815 timestamp, is_backlog);
817 DEBUG ("Couldn't find HTML file for this message");
820 /* Keep the sender of the last displayed message */
821 if (priv->last_contact) {
822 g_object_unref (priv->last_contact);
824 priv->last_contact = g_object_ref (sender);
825 priv->last_timestamp = timestamp;
826 priv->last_is_backlog = is_backlog;
828 g_free (body_escaped);
829 g_string_free (message_classes, TRUE);
833 theme_adium_append_event (EmpathyChatView *view,
838 str_escaped = g_markup_escape_text (str, -1);
839 theme_adium_append_event_escaped (view, str_escaped);
840 g_free (str_escaped);
844 theme_adium_scroll (EmpathyChatView *view,
845 gboolean allow_scrolling)
847 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
849 priv->allow_scrolling = allow_scrolling;
850 if (allow_scrolling) {
851 empathy_chat_view_scroll_down (view);
856 theme_adium_scroll_down (EmpathyChatView *view)
858 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
862 theme_adium_get_has_selection (EmpathyChatView *view)
864 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
868 theme_adium_clear (EmpathyChatView *view)
870 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
873 priv->pages_loading++;
874 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
875 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
876 priv->data->template_html,
878 g_free (basedir_uri);
880 /* Clear last contact to avoid trying to add a 'joined'
881 * message when we don't have an insertion point. */
882 if (priv->last_contact) {
883 g_object_unref (priv->last_contact);
884 priv->last_contact = NULL;
889 theme_adium_find_previous (EmpathyChatView *view,
890 const gchar *search_criteria,
894 /* FIXME: Doesn't respect new_search */
895 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
896 search_criteria, match_case,
901 theme_adium_find_next (EmpathyChatView *view,
902 const gchar *search_criteria,
906 /* FIXME: Doesn't respect new_search */
907 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
908 search_criteria, match_case,
913 theme_adium_find_abilities (EmpathyChatView *view,
914 const gchar *search_criteria,
916 gboolean *can_do_previous,
917 gboolean *can_do_next)
919 /* FIXME: Does webkit provide an API for that? We have wrap=true in
920 * find_next and find_previous to work around this problem. */
922 *can_do_previous = TRUE;
928 theme_adium_highlight (EmpathyChatView *view,
932 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
933 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
934 text, match_case, 0);
935 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
940 theme_adium_copy_clipboard (EmpathyChatView *view)
942 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
946 theme_adium_focus_toggled (EmpathyChatView *view,
949 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
951 priv->has_focus = has_focus;
952 if (priv->has_focus) {
953 priv->has_unread_message = FALSE;
958 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
960 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
962 g_object_unref (hit_test_result);
966 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
968 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
969 WebKitHitTestResult *hit_test_result;
970 WebKitHitTestResultContext context;
974 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
975 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
978 menu = empathy_context_menu_new (GTK_WIDGET (view));
980 /* Select all item */
981 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
982 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
984 g_signal_connect_swapped (item, "activate",
985 G_CALLBACK (webkit_web_view_select_all),
989 if (webkit_web_view_can_copy_clipboard (view)) {
990 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
991 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
993 g_signal_connect_swapped (item, "activate",
994 G_CALLBACK (webkit_web_view_copy_clipboard),
998 /* Clear menu item */
999 item = gtk_separator_menu_item_new ();
1000 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1002 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
1003 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1005 g_signal_connect_swapped (item, "activate",
1006 G_CALLBACK (empathy_chat_view_clear),
1009 /* We will only add the following menu items if we are
1010 * right-clicking a link */
1011 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1013 item = gtk_separator_menu_item_new ();
1014 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1016 /* Copy Link Address menu item */
1017 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1018 g_signal_connect (item, "activate",
1019 G_CALLBACK (theme_adium_copy_address_cb),
1021 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1023 /* Open Link menu item */
1024 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1025 g_signal_connect (item, "activate",
1026 G_CALLBACK (theme_adium_open_address_cb),
1028 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1031 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1032 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1035 /* Display the menu */
1036 gtk_widget_show_all (menu);
1037 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1038 event->button, event->time);
1042 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1044 if (event->button == 3) {
1045 gboolean developer_tools_enabled;
1047 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1048 "enable-developer-extras", &developer_tools_enabled, NULL);
1050 /* We currently have no way to add an inspector menu
1051 * item ourselves, so we disable our customized menu
1052 * if the developer extras are enabled. */
1053 if (!developer_tools_enabled) {
1054 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1059 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1063 theme_adium_iface_init (EmpathyChatViewIface *iface)
1065 iface->append_message = theme_adium_append_message;
1066 iface->append_event = theme_adium_append_event;
1067 iface->scroll = theme_adium_scroll;
1068 iface->scroll_down = theme_adium_scroll_down;
1069 iface->get_has_selection = theme_adium_get_has_selection;
1070 iface->clear = theme_adium_clear;
1071 iface->find_previous = theme_adium_find_previous;
1072 iface->find_next = theme_adium_find_next;
1073 iface->find_abilities = theme_adium_find_abilities;
1074 iface->highlight = theme_adium_highlight;
1075 iface->copy_clipboard = theme_adium_copy_clipboard;
1076 iface->focus_toggled = theme_adium_focus_toggled;
1080 theme_adium_load_finished_cb (WebKitWebView *view,
1081 WebKitWebFrame *frame,
1084 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1085 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1087 DEBUG ("Page loaded");
1088 priv->pages_loading--;
1090 if (priv->pages_loading != 0)
1093 /* Display queued messages */
1094 priv->message_queue = g_list_reverse (priv->message_queue);
1095 while (priv->message_queue) {
1096 EmpathyMessage *message = priv->message_queue->data;
1098 theme_adium_append_message (chat_view, message);
1099 priv->message_queue = g_list_remove (priv->message_queue, message);
1100 g_object_unref (message);
1105 theme_adium_finalize (GObject *object)
1107 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1109 empathy_adium_data_unref (priv->data);
1110 g_object_unref (priv->gsettings_chat);
1112 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1116 theme_adium_dispose (GObject *object)
1118 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1120 if (priv->smiley_manager) {
1121 g_object_unref (priv->smiley_manager);
1122 priv->smiley_manager = NULL;
1125 if (priv->last_contact) {
1126 g_object_unref (priv->last_contact);
1127 priv->last_contact = NULL;
1130 if (priv->inspector_window) {
1131 gtk_widget_destroy (priv->inspector_window);
1132 priv->inspector_window = NULL;
1135 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1139 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1140 EmpathyThemeAdium *theme)
1142 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1144 if (priv->inspector_window) {
1145 gtk_widget_show_all (priv->inspector_window);
1152 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1153 EmpathyThemeAdium *theme)
1155 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1157 if (priv->inspector_window) {
1158 gtk_widget_hide (priv->inspector_window);
1164 static WebKitWebView *
1165 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1166 WebKitWebView *web_view,
1167 EmpathyThemeAdium *theme)
1169 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1170 GtkWidget *scrolled_window;
1171 GtkWidget *inspector_web_view;
1173 if (!priv->inspector_window) {
1174 /* Create main window */
1175 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1176 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1178 g_signal_connect (priv->inspector_window, "delete-event",
1179 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1181 /* Pack a scrolled window */
1182 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1183 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1184 GTK_POLICY_AUTOMATIC,
1185 GTK_POLICY_AUTOMATIC);
1186 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1188 gtk_widget_show (scrolled_window);
1190 /* Pack a webview in the scrolled window. That webview will be
1191 * used to render the inspector tool. */
1192 inspector_web_view = webkit_web_view_new ();
1193 gtk_container_add (GTK_CONTAINER (scrolled_window),
1194 inspector_web_view);
1195 gtk_widget_show (scrolled_window);
1197 return WEBKIT_WEB_VIEW (inspector_web_view);
1203 static PangoFontDescription *
1204 theme_adium_get_default_font (void)
1206 GSettings *gsettings;
1207 PangoFontDescription *pango_fd;
1210 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1212 font_family = g_settings_get_string (gsettings,
1213 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1215 if (font_family == NULL)
1218 pango_fd = pango_font_description_from_string (font_family);
1219 g_free (font_family);
1220 g_object_unref (gsettings);
1225 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1229 g_object_set (w_settings, "default-font-family", name, NULL);
1230 g_object_set (w_settings, "default-font-size", size, NULL);
1234 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1236 PangoFontDescription *default_font_desc;
1237 GdkScreen *current_screen;
1239 gint pango_font_size = 0;
1241 default_font_desc = theme_adium_get_default_font ();
1242 if (default_font_desc == NULL)
1244 pango_font_size = pango_font_description_get_size (default_font_desc)
1246 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1247 current_screen = gdk_screen_get_default ();
1248 if (current_screen != NULL) {
1249 dpi = gdk_screen_get_resolution (current_screen);
1251 dpi = BORING_DPI_DEFAULT;
1253 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1255 theme_adium_set_webkit_font (w_settings,
1256 pango_font_description_get_family (default_font_desc),
1258 pango_font_description_free (default_font_desc);
1262 theme_adium_constructed (GObject *object)
1264 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1266 const gchar *font_family = NULL;
1268 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1269 WebKitWebSettings *webkit_settings;
1270 WebKitWebInspector *webkit_inspector;
1272 /* Set default settings */
1273 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1274 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1275 webkit_settings = webkit_web_view_get_settings (webkit_view);
1277 if (font_family && font_size) {
1278 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1280 theme_adium_set_default_font (webkit_settings);
1283 /* Setup webkit inspector */
1284 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1285 g_signal_connect (webkit_inspector, "inspect-web-view",
1286 G_CALLBACK (theme_adium_inspect_web_view_cb),
1288 g_signal_connect (webkit_inspector, "show-window",
1289 G_CALLBACK (theme_adium_inspector_show_window_cb),
1291 g_signal_connect (webkit_inspector, "close-window",
1292 G_CALLBACK (theme_adium_inspector_close_window_cb),
1296 priv->pages_loading = 1;
1298 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1299 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1300 priv->data->template_html,
1302 g_free (basedir_uri);
1306 theme_adium_get_property (GObject *object,
1311 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1314 case PROP_ADIUM_DATA:
1315 g_value_set_boxed (value, priv->data);
1318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1324 theme_adium_set_property (GObject *object,
1326 const GValue *value,
1329 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1332 case PROP_ADIUM_DATA:
1333 g_assert (priv->data == NULL);
1334 priv->data = g_value_dup_boxed (value);
1337 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1343 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1345 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1346 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1348 object_class->finalize = theme_adium_finalize;
1349 object_class->dispose = theme_adium_dispose;
1350 object_class->constructed = theme_adium_constructed;
1351 object_class->get_property = theme_adium_get_property;
1352 object_class->set_property = theme_adium_set_property;
1354 widget_class->button_press_event = theme_adium_button_press_event;
1356 g_object_class_install_property (object_class,
1358 g_param_spec_boxed ("adium-data",
1360 "Data for the adium theme",
1361 EMPATHY_TYPE_ADIUM_DATA,
1362 G_PARAM_CONSTRUCT_ONLY |
1364 G_PARAM_STATIC_STRINGS));
1366 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1370 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1372 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1373 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1377 priv->allow_scrolling = TRUE;
1378 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1380 g_signal_connect (theme, "load-finished",
1381 G_CALLBACK (theme_adium_load_finished_cb),
1383 g_signal_connect (theme, "navigation-policy-decision-requested",
1384 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1387 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1388 g_signal_connect (priv->gsettings_chat,
1389 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1390 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1393 theme_adium_update_enable_webkit_developer_tools (theme);
1397 empathy_theme_adium_new (EmpathyAdiumData *data)
1399 g_return_val_if_fail (data != NULL, NULL);
1401 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1407 empathy_adium_path_is_valid (const gchar *path)
1412 /* The theme is not valid if there is no Info.plist */
1413 file = g_build_filename (path, "Contents", "Info.plist",
1415 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1421 /* We ship a default Template.html as fallback if there is any problem
1422 * with the one inside the theme. The only other required file is
1424 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1426 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1432 /* Legacy themes have Incoming/Content.html (outgoing fallback to use
1434 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1435 "Content.html", NULL);
1436 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1443 empathy_adium_info_new (const gchar *path)
1447 GHashTable *info = NULL;
1449 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1451 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1452 value = empathy_plist_parse_from_file (file);
1458 info = g_value_dup_boxed (value);
1459 tp_g_value_slice_free (value);
1461 /* Insert the theme's path into the hash table,
1462 * keys have to be dupped */
1463 tp_asv_set_string (info, g_strdup ("path"), path);
1469 empathy_adium_data_get_type (void)
1471 static GType type_id = 0;
1475 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1476 (GBoxedCopyFunc) empathy_adium_data_ref,
1477 (GBoxedFreeFunc) empathy_adium_data_unref);
1484 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1486 EmpathyAdiumData *data;
1488 gchar *template_html = NULL;
1490 gchar *footer_html = NULL;
1493 gchar **strv = NULL;
1498 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1500 data = g_slice_new0 (EmpathyAdiumData);
1501 data->ref_count = 1;
1502 data->path = g_strdup (path);
1503 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1504 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1505 data->info = g_hash_table_ref (info);
1507 DEBUG ("Loading theme at %s", path);
1509 /* Load html files */
1510 file = g_build_filename (data->basedir, "Content.html", NULL);
1511 g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
1514 /* Fallback to legacy html files */
1515 if (data->content_html == NULL) {
1516 DEBUG (" fallback to legacy theme");
1518 file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1519 g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1522 file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1523 g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1526 file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1527 g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1530 file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1531 g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1534 file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1535 g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1538 file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1539 g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1542 file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1543 g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1546 file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1547 g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1550 file = g_build_filename (data->basedir, "Status.html", NULL);
1551 g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1555 file = g_build_filename (data->basedir, "Footer.html", NULL);
1556 g_file_get_contents (file, &footer_html, &footer_len, NULL);
1559 file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1560 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1561 data->default_incoming_avatar_filename = file;
1566 file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1567 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1568 data->default_outgoing_avatar_filename = file;
1573 css_path = g_build_filename (data->basedir, "main.css", NULL);
1575 /* There is 2 formats for Template.html: The old one has 4 parameters,
1576 * the new one has 5 parameters. */
1577 file = g_build_filename (data->basedir, "Template.html", NULL);
1578 if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1579 strv = g_strsplit (template_html, "%@", -1);
1580 len = g_strv_length (strv);
1584 if (len != 5 && len != 6) {
1585 /* Either the theme has no template or it don't have the good
1586 * number of parameters. Fallback to use our own template. */
1587 g_free (template_html);
1590 file = empathy_file_lookup ("Template.html", "data");
1591 g_file_get_contents (file, &template_html, &template_len, NULL);
1593 strv = g_strsplit (template_html, "%@", -1);
1594 len = g_strv_length (strv);
1597 /* Replace %@ with the needed information in the template html. */
1598 string = g_string_sized_new (template_len);
1599 g_string_append (string, strv[i++]);
1600 g_string_append (string, data->basedir);
1601 g_string_append (string, strv[i++]);
1603 const gchar *variant;
1605 /* We include main.css by default */
1606 g_string_append_printf (string, "@import url(\"%s\");", css_path);
1607 g_string_append (string, strv[i++]);
1608 variant = tp_asv_get_string (data->info, "DefaultVariant");
1610 g_string_append (string, "Variants/");
1611 g_string_append (string, variant);
1612 g_string_append (string, ".css");
1615 /* FIXME: We should set main.css OR the variant css */
1616 g_string_append (string, css_path);
1618 g_string_append (string, strv[i++]);
1619 g_string_append (string, ""); /* We don't want header */
1620 g_string_append (string, strv[i++]);
1621 /* FIXME: We should replace adium %macros% in footer */
1623 g_string_append (string, footer_html);
1625 g_string_append (string, strv[i++]);
1626 data->template_html = g_string_free (string, FALSE);
1628 g_free (footer_html);
1629 g_free (template_html);
1637 empathy_adium_data_new (const gchar *path)
1639 EmpathyAdiumData *data;
1642 info = empathy_adium_info_new (path);
1643 data = empathy_adium_data_new_with_info (path, info);
1644 g_hash_table_unref (info);
1650 empathy_adium_data_ref (EmpathyAdiumData *data)
1652 g_return_val_if_fail (data != NULL, NULL);
1654 g_atomic_int_inc (&data->ref_count);
1660 empathy_adium_data_unref (EmpathyAdiumData *data)
1662 g_return_if_fail (data != NULL);
1664 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1665 g_free (data->path);
1666 g_free (data->basedir);
1667 g_free (data->default_avatar_filename);
1668 g_free (data->default_incoming_avatar_filename);
1669 g_free (data->default_outgoing_avatar_filename);
1670 g_free (data->template_html);
1671 g_free (data->content_html);
1672 g_hash_table_unref (data->info);
1674 g_free (data->in_content_html);
1675 g_free (data->in_nextcontent_html);
1676 g_free (data->in_context_html);
1677 g_free (data->in_nextcontext_html);
1678 g_free (data->out_content_html);
1679 g_free (data->out_nextcontent_html);
1680 g_free (data->out_context_html);
1681 g_free (data->out_nextcontext_html);
1682 g_free (data->status_html);
1684 g_slice_free (EmpathyAdiumData, data);
1689 empathy_adium_data_get_info (EmpathyAdiumData *data)
1691 g_return_val_if_fail (data != NULL, NULL);
1697 empathy_adium_data_get_path (EmpathyAdiumData *data)
1699 g_return_val_if_fail (data != NULL, NULL);