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,
836 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
839 if (priv->pages_loading != 0) {
840 /* FIXME: events should be queued until page loaded */
841 DEBUG ("Error appending event (%s): Page not loaded", str);
845 str_escaped = g_markup_escape_text (str, -1);
846 theme_adium_append_event_escaped (view, str_escaped);
847 g_free (str_escaped);
851 theme_adium_scroll (EmpathyChatView *view,
852 gboolean allow_scrolling)
854 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
856 priv->allow_scrolling = allow_scrolling;
857 if (allow_scrolling) {
858 empathy_chat_view_scroll_down (view);
863 theme_adium_scroll_down (EmpathyChatView *view)
865 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
869 theme_adium_get_has_selection (EmpathyChatView *view)
871 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
875 theme_adium_clear (EmpathyChatView *view)
877 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
880 priv->pages_loading++;
881 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
882 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
883 priv->data->template_html,
885 g_free (basedir_uri);
887 /* Clear last contact to avoid trying to add a 'joined'
888 * message when we don't have an insertion point. */
889 if (priv->last_contact) {
890 g_object_unref (priv->last_contact);
891 priv->last_contact = NULL;
896 theme_adium_find_previous (EmpathyChatView *view,
897 const gchar *search_criteria,
901 /* FIXME: Doesn't respect new_search */
902 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
903 search_criteria, match_case,
908 theme_adium_find_next (EmpathyChatView *view,
909 const gchar *search_criteria,
913 /* FIXME: Doesn't respect new_search */
914 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
915 search_criteria, match_case,
920 theme_adium_find_abilities (EmpathyChatView *view,
921 const gchar *search_criteria,
923 gboolean *can_do_previous,
924 gboolean *can_do_next)
926 /* FIXME: Does webkit provide an API for that? We have wrap=true in
927 * find_next and find_previous to work around this problem. */
929 *can_do_previous = TRUE;
935 theme_adium_highlight (EmpathyChatView *view,
939 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
940 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
941 text, match_case, 0);
942 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
947 theme_adium_copy_clipboard (EmpathyChatView *view)
949 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
953 theme_adium_focus_toggled (EmpathyChatView *view,
956 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
958 priv->has_focus = has_focus;
959 if (priv->has_focus) {
960 priv->has_unread_message = FALSE;
965 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
967 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
969 g_object_unref (hit_test_result);
973 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
975 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
976 WebKitHitTestResult *hit_test_result;
977 WebKitHitTestResultContext context;
981 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
982 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
985 menu = empathy_context_menu_new (GTK_WIDGET (view));
987 /* Select all item */
988 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
989 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
991 g_signal_connect_swapped (item, "activate",
992 G_CALLBACK (webkit_web_view_select_all),
996 if (webkit_web_view_can_copy_clipboard (view)) {
997 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
998 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1000 g_signal_connect_swapped (item, "activate",
1001 G_CALLBACK (webkit_web_view_copy_clipboard),
1005 /* Clear menu item */
1006 item = gtk_separator_menu_item_new ();
1007 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1009 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
1010 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1012 g_signal_connect_swapped (item, "activate",
1013 G_CALLBACK (empathy_chat_view_clear),
1016 /* We will only add the following menu items if we are
1017 * right-clicking a link */
1018 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1020 item = gtk_separator_menu_item_new ();
1021 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1023 /* Copy Link Address menu item */
1024 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1025 g_signal_connect (item, "activate",
1026 G_CALLBACK (theme_adium_copy_address_cb),
1028 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1030 /* Open Link menu item */
1031 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1032 g_signal_connect (item, "activate",
1033 G_CALLBACK (theme_adium_open_address_cb),
1035 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1038 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1039 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1042 /* Display the menu */
1043 gtk_widget_show_all (menu);
1044 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1045 event->button, event->time);
1049 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1051 if (event->button == 3) {
1052 gboolean developer_tools_enabled;
1054 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1055 "enable-developer-extras", &developer_tools_enabled, NULL);
1057 /* We currently have no way to add an inspector menu
1058 * item ourselves, so we disable our customized menu
1059 * if the developer extras are enabled. */
1060 if (!developer_tools_enabled) {
1061 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1066 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1070 theme_adium_iface_init (EmpathyChatViewIface *iface)
1072 iface->append_message = theme_adium_append_message;
1073 iface->append_event = theme_adium_append_event;
1074 iface->scroll = theme_adium_scroll;
1075 iface->scroll_down = theme_adium_scroll_down;
1076 iface->get_has_selection = theme_adium_get_has_selection;
1077 iface->clear = theme_adium_clear;
1078 iface->find_previous = theme_adium_find_previous;
1079 iface->find_next = theme_adium_find_next;
1080 iface->find_abilities = theme_adium_find_abilities;
1081 iface->highlight = theme_adium_highlight;
1082 iface->copy_clipboard = theme_adium_copy_clipboard;
1083 iface->focus_toggled = theme_adium_focus_toggled;
1087 theme_adium_load_finished_cb (WebKitWebView *view,
1088 WebKitWebFrame *frame,
1091 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1092 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1094 DEBUG ("Page loaded");
1095 priv->pages_loading--;
1097 if (priv->pages_loading != 0)
1100 /* Display queued messages */
1101 priv->message_queue = g_list_reverse (priv->message_queue);
1102 while (priv->message_queue) {
1103 EmpathyMessage *message = priv->message_queue->data;
1105 theme_adium_append_message (chat_view, message);
1106 priv->message_queue = g_list_remove (priv->message_queue, message);
1107 g_object_unref (message);
1112 theme_adium_finalize (GObject *object)
1114 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1116 empathy_adium_data_unref (priv->data);
1117 g_object_unref (priv->gsettings_chat);
1119 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1123 theme_adium_dispose (GObject *object)
1125 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1127 if (priv->smiley_manager) {
1128 g_object_unref (priv->smiley_manager);
1129 priv->smiley_manager = NULL;
1132 if (priv->last_contact) {
1133 g_object_unref (priv->last_contact);
1134 priv->last_contact = NULL;
1137 if (priv->inspector_window) {
1138 gtk_widget_destroy (priv->inspector_window);
1139 priv->inspector_window = NULL;
1142 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1146 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1147 EmpathyThemeAdium *theme)
1149 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1151 if (priv->inspector_window) {
1152 gtk_widget_show_all (priv->inspector_window);
1159 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1160 EmpathyThemeAdium *theme)
1162 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1164 if (priv->inspector_window) {
1165 gtk_widget_hide (priv->inspector_window);
1171 static WebKitWebView *
1172 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1173 WebKitWebView *web_view,
1174 EmpathyThemeAdium *theme)
1176 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1177 GtkWidget *scrolled_window;
1178 GtkWidget *inspector_web_view;
1180 if (!priv->inspector_window) {
1181 /* Create main window */
1182 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1183 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1185 g_signal_connect (priv->inspector_window, "delete-event",
1186 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1188 /* Pack a scrolled window */
1189 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1190 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1191 GTK_POLICY_AUTOMATIC,
1192 GTK_POLICY_AUTOMATIC);
1193 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1195 gtk_widget_show (scrolled_window);
1197 /* Pack a webview in the scrolled window. That webview will be
1198 * used to render the inspector tool. */
1199 inspector_web_view = webkit_web_view_new ();
1200 gtk_container_add (GTK_CONTAINER (scrolled_window),
1201 inspector_web_view);
1202 gtk_widget_show (scrolled_window);
1204 return WEBKIT_WEB_VIEW (inspector_web_view);
1210 static PangoFontDescription *
1211 theme_adium_get_default_font (void)
1213 GSettings *gsettings;
1214 PangoFontDescription *pango_fd;
1217 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1219 font_family = g_settings_get_string (gsettings,
1220 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1222 if (font_family == NULL)
1225 pango_fd = pango_font_description_from_string (font_family);
1226 g_free (font_family);
1227 g_object_unref (gsettings);
1232 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1236 g_object_set (w_settings, "default-font-family", name, NULL);
1237 g_object_set (w_settings, "default-font-size", size, NULL);
1241 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1243 PangoFontDescription *default_font_desc;
1244 GdkScreen *current_screen;
1246 gint pango_font_size = 0;
1248 default_font_desc = theme_adium_get_default_font ();
1249 if (default_font_desc == NULL)
1251 pango_font_size = pango_font_description_get_size (default_font_desc)
1253 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1254 current_screen = gdk_screen_get_default ();
1255 if (current_screen != NULL) {
1256 dpi = gdk_screen_get_resolution (current_screen);
1258 dpi = BORING_DPI_DEFAULT;
1260 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1262 theme_adium_set_webkit_font (w_settings,
1263 pango_font_description_get_family (default_font_desc),
1265 pango_font_description_free (default_font_desc);
1269 theme_adium_constructed (GObject *object)
1271 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1273 const gchar *font_family = NULL;
1275 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1276 WebKitWebSettings *webkit_settings;
1277 WebKitWebInspector *webkit_inspector;
1279 /* Set default settings */
1280 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1281 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1282 webkit_settings = webkit_web_view_get_settings (webkit_view);
1284 if (font_family && font_size) {
1285 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1287 theme_adium_set_default_font (webkit_settings);
1290 /* Setup webkit inspector */
1291 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1292 g_signal_connect (webkit_inspector, "inspect-web-view",
1293 G_CALLBACK (theme_adium_inspect_web_view_cb),
1295 g_signal_connect (webkit_inspector, "show-window",
1296 G_CALLBACK (theme_adium_inspector_show_window_cb),
1298 g_signal_connect (webkit_inspector, "close-window",
1299 G_CALLBACK (theme_adium_inspector_close_window_cb),
1303 priv->pages_loading = 1;
1305 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
1306 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
1307 priv->data->template_html,
1309 g_free (basedir_uri);
1313 theme_adium_get_property (GObject *object,
1318 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1321 case PROP_ADIUM_DATA:
1322 g_value_set_boxed (value, priv->data);
1325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1331 theme_adium_set_property (GObject *object,
1333 const GValue *value,
1336 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1339 case PROP_ADIUM_DATA:
1340 g_assert (priv->data == NULL);
1341 priv->data = g_value_dup_boxed (value);
1344 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1350 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1352 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1353 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1355 object_class->finalize = theme_adium_finalize;
1356 object_class->dispose = theme_adium_dispose;
1357 object_class->constructed = theme_adium_constructed;
1358 object_class->get_property = theme_adium_get_property;
1359 object_class->set_property = theme_adium_set_property;
1361 widget_class->button_press_event = theme_adium_button_press_event;
1363 g_object_class_install_property (object_class,
1365 g_param_spec_boxed ("adium-data",
1367 "Data for the adium theme",
1368 EMPATHY_TYPE_ADIUM_DATA,
1369 G_PARAM_CONSTRUCT_ONLY |
1371 G_PARAM_STATIC_STRINGS));
1373 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1377 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1379 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1380 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1384 priv->allow_scrolling = TRUE;
1385 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1387 g_signal_connect (theme, "load-finished",
1388 G_CALLBACK (theme_adium_load_finished_cb),
1390 g_signal_connect (theme, "navigation-policy-decision-requested",
1391 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1394 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1395 g_signal_connect (priv->gsettings_chat,
1396 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1397 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1400 theme_adium_update_enable_webkit_developer_tools (theme);
1404 empathy_theme_adium_new (EmpathyAdiumData *data)
1406 g_return_val_if_fail (data != NULL, NULL);
1408 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1414 empathy_adium_path_is_valid (const gchar *path)
1419 /* The theme is not valid if there is no Info.plist */
1420 file = g_build_filename (path, "Contents", "Info.plist",
1422 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1428 /* We ship a default Template.html as fallback if there is any problem
1429 * with the one inside the theme. The only other required file is
1431 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1433 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1439 /* Legacy themes have Incoming/Content.html (outgoing fallback to use
1441 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1442 "Content.html", NULL);
1443 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1450 empathy_adium_info_new (const gchar *path)
1454 GHashTable *info = NULL;
1456 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1458 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1459 value = empathy_plist_parse_from_file (file);
1465 info = g_value_dup_boxed (value);
1466 tp_g_value_slice_free (value);
1468 /* Insert the theme's path into the hash table,
1469 * keys have to be dupped */
1470 tp_asv_set_string (info, g_strdup ("path"), path);
1476 empathy_adium_data_get_type (void)
1478 static GType type_id = 0;
1482 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1483 (GBoxedCopyFunc) empathy_adium_data_ref,
1484 (GBoxedFreeFunc) empathy_adium_data_unref);
1491 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1493 EmpathyAdiumData *data;
1495 gchar *template_html = NULL;
1497 gchar *footer_html = NULL;
1500 gchar **strv = NULL;
1505 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1507 data = g_slice_new0 (EmpathyAdiumData);
1508 data->ref_count = 1;
1509 data->path = g_strdup (path);
1510 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1511 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1512 data->info = g_hash_table_ref (info);
1514 DEBUG ("Loading theme at %s", path);
1516 /* Load html files */
1517 file = g_build_filename (data->basedir, "Content.html", NULL);
1518 g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
1521 /* Fallback to legacy html files */
1522 if (data->content_html == NULL) {
1523 DEBUG (" fallback to legacy theme");
1525 file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1526 g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1529 file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1530 g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1533 file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1534 g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1537 file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1538 g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1541 file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1542 g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1545 file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1546 g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1549 file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1550 g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1553 file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1554 g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1557 file = g_build_filename (data->basedir, "Status.html", NULL);
1558 g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1562 file = g_build_filename (data->basedir, "Footer.html", NULL);
1563 g_file_get_contents (file, &footer_html, &footer_len, NULL);
1566 file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1567 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1568 data->default_incoming_avatar_filename = file;
1573 file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1574 if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1575 data->default_outgoing_avatar_filename = file;
1580 css_path = g_build_filename (data->basedir, "main.css", NULL);
1582 /* There is 2 formats for Template.html: The old one has 4 parameters,
1583 * the new one has 5 parameters. */
1584 file = g_build_filename (data->basedir, "Template.html", NULL);
1585 if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1586 strv = g_strsplit (template_html, "%@", -1);
1587 len = g_strv_length (strv);
1591 if (len != 5 && len != 6) {
1592 /* Either the theme has no template or it don't have the good
1593 * number of parameters. Fallback to use our own template. */
1594 g_free (template_html);
1597 file = empathy_file_lookup ("Template.html", "data");
1598 g_file_get_contents (file, &template_html, &template_len, NULL);
1600 strv = g_strsplit (template_html, "%@", -1);
1601 len = g_strv_length (strv);
1604 /* Replace %@ with the needed information in the template html. */
1605 string = g_string_sized_new (template_len);
1606 g_string_append (string, strv[i++]);
1607 g_string_append (string, data->basedir);
1608 g_string_append (string, strv[i++]);
1610 const gchar *variant;
1612 /* We include main.css by default */
1613 g_string_append_printf (string, "@import url(\"%s\");", css_path);
1614 g_string_append (string, strv[i++]);
1615 variant = tp_asv_get_string (data->info, "DefaultVariant");
1617 g_string_append (string, "Variants/");
1618 g_string_append (string, variant);
1619 g_string_append (string, ".css");
1622 /* FIXME: We should set main.css OR the variant css */
1623 g_string_append (string, css_path);
1625 g_string_append (string, strv[i++]);
1626 g_string_append (string, ""); /* We don't want header */
1627 g_string_append (string, strv[i++]);
1628 /* FIXME: We should replace adium %macros% in footer */
1630 g_string_append (string, footer_html);
1632 g_string_append (string, strv[i++]);
1633 data->template_html = g_string_free (string, FALSE);
1635 g_free (footer_html);
1636 g_free (template_html);
1644 empathy_adium_data_new (const gchar *path)
1646 EmpathyAdiumData *data;
1649 info = empathy_adium_info_new (path);
1650 data = empathy_adium_data_new_with_info (path, info);
1651 g_hash_table_unref (info);
1657 empathy_adium_data_ref (EmpathyAdiumData *data)
1659 g_return_val_if_fail (data != NULL, NULL);
1661 g_atomic_int_inc (&data->ref_count);
1667 empathy_adium_data_unref (EmpathyAdiumData *data)
1669 g_return_if_fail (data != NULL);
1671 if (g_atomic_int_dec_and_test (&data->ref_count)) {
1672 g_free (data->path);
1673 g_free (data->basedir);
1674 g_free (data->default_avatar_filename);
1675 g_free (data->default_incoming_avatar_filename);
1676 g_free (data->default_outgoing_avatar_filename);
1677 g_free (data->template_html);
1678 g_free (data->content_html);
1679 g_hash_table_unref (data->info);
1681 g_free (data->in_content_html);
1682 g_free (data->in_nextcontent_html);
1683 g_free (data->in_context_html);
1684 g_free (data->in_nextcontext_html);
1685 g_free (data->out_content_html);
1686 g_free (data->out_nextcontent_html);
1687 g_free (data->out_context_html);
1688 g_free (data->out_nextcontext_html);
1689 g_free (data->status_html);
1691 g_slice_free (EmpathyAdiumData, data);
1696 empathy_adium_data_get_info (EmpathyAdiumData *data)
1698 g_return_val_if_fail (data != NULL, NULL);
1704 empathy_adium_data_get_path (EmpathyAdiumData *data)
1706 g_return_val_if_fail (data != NULL, NULL);