]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-theme-adium.c
Merge commit 'upstream/master' into mc5
[empathy.git] / libempathy-gtk / empathy-theme-adium.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008-2009 Collabora Ltd.
4  *
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.
9  *
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.
14  *
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
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <glib/gi18n.h>
26
27 #include <webkit/webkitnetworkrequest.h>
28 #include <telepathy-glib/dbus.h>
29 #include <telepathy-glib/util.h>
30
31
32 #include <libempathy/empathy-time.h>
33 #include <libempathy/empathy-utils.h>
34
35 #include "empathy-theme-adium.h"
36 #include "empathy-smiley-manager.h"
37 #include "empathy-conf.h"
38 #include "empathy-ui-utils.h"
39 #include "empathy-plist.h"
40
41 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
42 #include <libempathy/empathy-debug.h>
43
44 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
45
46 /* "Join" consecutive messages with timestamps within five minutes */
47 #define MESSAGE_JOIN_PERIOD 5*60
48
49 typedef struct {
50         EmpathyAdiumData     *data;
51         EmpathySmileyManager *smiley_manager;
52         EmpathyContact       *last_contact;
53         time_t                last_timestamp;
54         gboolean              last_is_backlog;
55         gboolean              page_loaded;
56         GList                *message_queue;
57         gchar                *hovered_uri;
58         guint                 notify_enable_webkit_developer_tools_id;
59         GtkWidget            *inspector_window;
60 } EmpathyThemeAdiumPriv;
61
62 struct _EmpathyAdiumData {
63         guint  ref_count;
64         gchar *path;
65         gchar *basedir;
66         gchar *default_avatar_filename;
67         gchar *default_incoming_avatar_filename;
68         gchar *default_outgoing_avatar_filename;
69         gchar *template_html;
70         gchar *in_content_html;
71         gsize  in_content_len;
72         gchar *in_context_html;
73         gsize  in_context_len;
74         gchar *in_nextcontent_html;
75         gsize  in_nextcontent_len;
76         gchar *in_nextcontext_html;
77         gsize  in_nextcontext_len;
78         gchar *out_content_html;
79         gsize  out_content_len;
80         gchar *out_context_html;
81         gsize  out_context_len;
82         gchar *out_nextcontent_html;
83         gsize  out_nextcontent_len;
84         gchar *out_nextcontext_html;
85         gsize  out_nextcontext_len;
86         gchar *status_html;
87         gsize  status_len;
88         GHashTable *info;
89 };
90
91 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
92
93 enum {
94         PROP_0,
95         PROP_ADIUM_DATA,
96 };
97
98 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
99                          WEBKIT_TYPE_WEB_VIEW,
100                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
101                                                 theme_adium_iface_init));
102
103 static void
104 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
105 {
106         WebKitWebView  *web_view = WEBKIT_WEB_VIEW (theme);
107         gboolean        enable_webkit_developer_tools;
108
109         if (empathy_conf_get_bool (empathy_conf_get (), "/apps/empathy/conversation/enable_webkit_developer_tools", &enable_webkit_developer_tools) == FALSE)
110                 return;
111
112         g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
113                       "enable-developer-extras",
114                       enable_webkit_developer_tools,
115                       NULL);
116 }
117
118 static void
119 theme_adium_notify_enable_webkit_developer_tools_cb (EmpathyConf *conf,
120                                                      const gchar *key,
121                                                      gpointer     user_data)
122 {
123         EmpathyThemeAdium  *theme = user_data;
124
125         theme_adium_update_enable_webkit_developer_tools (theme);
126 }
127
128 static WebKitNavigationResponse
129 theme_adium_navigation_requested_cb (WebKitWebView        *view,
130                                      WebKitWebFrame       *frame,
131                                      WebKitNetworkRequest *request,
132                                      gpointer              user_data)
133 {
134         const gchar *uri;
135
136         uri = webkit_network_request_get_uri (request);
137         empathy_url_show (GTK_WIDGET (view), uri);
138
139         return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
140 }
141
142 static void
143 theme_adium_hovering_over_link_cb (EmpathyThemeAdium *theme,
144                                    gchar             *title,
145                                    gchar             *uri,
146                                    gpointer           user_data)
147 {
148         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
149
150         if (tp_strdiff (uri, priv->hovered_uri)) {
151                 g_free (priv->hovered_uri);
152                 priv->hovered_uri = g_strdup (uri);
153         }
154 }
155
156 static void
157 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
158                              gpointer     user_data)
159 {
160         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (user_data);
161         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
162         GtkClipboard          *clipboard;
163
164         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
165         gtk_clipboard_set_text (clipboard, priv->hovered_uri, -1);
166
167         clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
168         gtk_clipboard_set_text (clipboard, priv->hovered_uri, -1);
169 }
170
171 static void
172 theme_adium_open_address_cb (GtkMenuItem *menuitem,
173                              gpointer     user_data)
174 {
175         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (user_data);
176         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
177
178         empathy_url_show (GTK_WIDGET (menuitem), priv->hovered_uri);
179 }
180
181 static void
182 theme_adium_populate_popup_cb (WebKitWebView *view,
183                                GtkMenu       *menu,
184                                gpointer       user_data)
185 {
186         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
187         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
188         GtkWidget             *item;
189         GList                 *items;
190         GtkWidget             *icon;
191         gchar                 *stock_id;
192         gboolean               is_link = FALSE;
193         gboolean               developer_tools_enabled;
194
195         /* FIXME: WebKitGTK+'s context menu API clearly needs an
196          * overhaul.  There is currently no way to know what is being
197          * clicked, to decide what features to provide. You either
198          * take what it gives you as a menu, or use hacks to figure
199          * out what to display. */
200         items = gtk_container_get_children (GTK_CONTAINER (menu));
201         item = GTK_WIDGET (g_list_nth_data (items, 0));
202         g_list_free (items);
203
204         if (GTK_IS_IMAGE_MENU_ITEM (item)) {
205                 icon = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (item));
206                 gtk_image_get_stock (GTK_IMAGE (icon), &stock_id, NULL);
207
208                 if ((!strcmp (stock_id, GTK_STOCK_OPEN)) && priv->hovered_uri)
209                         is_link = TRUE;
210         }
211
212         /* Remove default menu items */
213         g_object_get (G_OBJECT (webkit_web_view_get_settings (view)),
214                       "enable-developer-extras", &developer_tools_enabled, NULL);
215         if (!developer_tools_enabled)
216                 gtk_container_foreach (GTK_CONTAINER (menu),
217                                        (GtkCallback) gtk_widget_destroy, NULL);
218
219         /* Select all item */
220         item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
221         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
222         gtk_widget_show (item);
223
224         g_signal_connect_swapped (item, "activate",
225                                   G_CALLBACK (webkit_web_view_select_all),
226                                   view);
227
228         /* Copy menu item */
229         if (webkit_web_view_can_copy_clipboard (view)) {
230                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
231                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
232                 gtk_widget_show (item);
233
234                 g_signal_connect_swapped (item, "activate",
235                                           G_CALLBACK (webkit_web_view_copy_clipboard),
236                                           view);
237         }
238
239         /* Clear menu item */
240         item = gtk_separator_menu_item_new ();
241         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
242         gtk_widget_show (item);
243
244         item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
245         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
246         gtk_widget_show (item);
247
248         g_signal_connect_swapped (item, "activate",
249                                   G_CALLBACK (empathy_chat_view_clear),
250                                   view);
251
252         /* We will only add the following menu items if we are
253          * right-clicking a link */
254         if (!is_link)
255                 return;
256
257         /* Separator */
258         item = gtk_separator_menu_item_new ();
259         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
260         gtk_widget_show (item);
261
262         /* Copy Link Address menu item */
263         item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
264         g_signal_connect (item, "activate",
265                           G_CALLBACK (theme_adium_copy_address_cb),
266                           view);
267         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
268         gtk_widget_show (item);
269
270         /* Open Link menu item */
271         item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
272         g_signal_connect (item, "activate",
273                           G_CALLBACK (theme_adium_open_address_cb),
274                           view);
275         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
276         gtk_widget_show (item);
277 }
278
279 static gchar *
280 theme_adium_parse_body (EmpathyThemeAdium *theme,
281                         const gchar       *text)
282 {
283         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
284         gboolean               use_smileys = FALSE;
285         GSList                *smileys, *l;
286         GString               *string;
287         gint                   i;
288         GRegex                *uri_regex;
289         GMatchInfo            *match_info;
290         gboolean               match;
291         gchar                 *ret = NULL;
292         gint                   prev;
293
294         empathy_conf_get_bool (empathy_conf_get (),
295                                EMPATHY_PREFS_CHAT_SHOW_SMILEYS,
296                                &use_smileys);
297
298         if (use_smileys) {
299                 /* Replace smileys by a <img/> tag */
300                 string = g_string_sized_new (strlen (text));
301                 smileys = empathy_smiley_manager_parse (priv->smiley_manager, text);
302                 for (l = smileys; l; l = l->next) {
303                         EmpathySmiley *smiley;
304
305                         smiley = l->data;
306                         if (smiley->path) {
307                                 g_string_append_printf (string,
308                                                         "<abbr title='%s'><img src=\"%s\"/ alt=\"%s\"/></abbr>",
309                                                         smiley->str, smiley->path, smiley->str);
310                         } else {
311                                 gchar *str;
312
313                                 str = g_markup_escape_text (smiley->str, -1);
314                                 g_string_append (string, str);
315                                 g_free (str);
316                         }
317                         empathy_smiley_free (smiley);
318                 }
319                 g_slist_free (smileys);
320
321                 g_free (ret);
322                 text = ret = g_string_free (string, FALSE);
323         }
324
325         /* Add <a href></a> arround links */
326         uri_regex = empathy_uri_regex_dup_singleton ();
327         match = g_regex_match (uri_regex, text, 0, &match_info);
328         if (match) {
329                 gint last = 0;
330                 gint s = 0, e = 0;
331
332                 string = g_string_sized_new (strlen (text));
333                 do {
334                         g_match_info_fetch_pos (match_info, 0, &s, &e);
335
336                         if (s > last) {
337                                 /* Append the text between last link (or the
338                                  * start of the message) and this link */
339                                 g_string_append_len (string, text + last, s - last);
340                         }
341
342                         /* Append the link inside <a href=""></a> tag */
343                         g_string_append (string, "<a href=\"");
344                         g_string_append_len (string, text + s, e - s);
345                         g_string_append (string, "\">");
346                         g_string_append_len (string, text + s, e - s);
347                         g_string_append (string, "</a>");
348
349                         last = e;
350                 } while (g_match_info_next (match_info, NULL));
351
352                 if (e < strlen (text)) {
353                         /* Append the text after the last link */
354                         g_string_append_len (string, text + e, strlen (text) - e);
355                 }
356
357                 g_free (ret);
358                 text = ret = g_string_free (string, FALSE);
359         }
360         g_match_info_free (match_info);
361         g_regex_unref (uri_regex);
362
363         /* Replace \n by <br/> */
364         string = NULL;
365         prev = 0;
366         for (i = 0; text[i] != '\0'; i++) {
367                 if (text[i] == '\n') {
368                         if (!string ) {
369                                 string = g_string_sized_new (strlen (text));
370                         }
371                         g_string_append_len (string, text + prev, i - prev);
372                         g_string_append (string, "<br/>");
373                         prev = i + 1;
374                 }
375         }
376         if (string) {
377                 g_string_append (string, text + prev);
378                 g_free (ret);
379                 text = ret = g_string_free (string, FALSE);
380         }
381
382         return ret;
383 }
384
385 static void
386 escape_and_append_len (GString *string, const gchar *str, gint len)
387 {
388         while (*str != '\0' && len != 0) {
389                 switch (*str) {
390                 case '\\':
391                         /* \ becomes \\ */
392                         g_string_append (string, "\\\\");
393                         break;
394                 case '\"':
395                         /* " becomes \" */
396                         g_string_append (string, "\\\"");
397                         break;
398                 case '\n':
399                         /* Remove end of lines */
400                         break;
401                 default:
402                         g_string_append_c (string, *str);
403                 }
404
405                 str++;
406                 len--;
407         }
408 }
409
410 static gboolean
411 theme_adium_match (const gchar **str, const gchar *match)
412 {
413         gint len;
414
415         len = strlen (match);
416         if (strncmp (*str, match, len) == 0) {
417                 *str += len - 1;
418                 return TRUE;
419         }
420
421         return FALSE;
422 }
423
424 static void
425 theme_adium_append_html (EmpathyThemeAdium *theme,
426                          const gchar       *func,
427                          const gchar       *html, gsize len,
428                          const gchar       *message,
429                          const gchar       *avatar_filename,
430                          const gchar       *name,
431                          const gchar       *contact_id,
432                          const gchar       *service_name,
433                          const gchar       *message_classes,
434                          time_t             timestamp)
435 {
436         GString     *string;
437         const gchar *cur = NULL;
438         gchar       *script;
439
440         /* Make some search-and-replace in the html code */
441         string = g_string_sized_new (len + strlen (message));
442         g_string_append_printf (string, "%s(\"", func);
443         for (cur = html; *cur != '\0'; cur++) {
444                 const gchar *replace = NULL;
445                 gchar       *dup_replace = NULL;
446
447                 if (theme_adium_match (&cur, "%message%")) {
448                         replace = message;
449                 } else if (theme_adium_match (&cur, "%messageClasses%")) {
450                         replace = message_classes;
451                 } else if (theme_adium_match (&cur, "%userIconPath%")) {
452                         replace = avatar_filename;
453                 } else if (theme_adium_match (&cur, "%sender%")) {
454                         replace = name;
455                 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
456                         replace = contact_id;
457                 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
458                         /* %senderDisplayName% -
459                          * "The serverside (remotely set) name of the sender,
460                          *  such as an MSN display name."
461                          *
462                          * We don't have access to that yet so we use local
463                          * alias instead.*/
464                         replace = name;
465                 } else if (theme_adium_match (&cur, "%service%")) {
466                         replace = service_name;
467                 } else if (theme_adium_match (&cur, "%shortTime%")) {
468                         dup_replace = empathy_time_to_string_local (timestamp,
469                                 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
470                         replace = dup_replace;
471                 } else if (theme_adium_match (&cur, "%time")) {
472                         gchar *format = NULL;
473                         gchar *end;
474                         /* Time can be in 2 formats:
475                          * %time% or %time{strftime format}%
476                          * Extract the time format if provided. */
477                         if (cur[1] == '{') {
478                                 cur += 2;
479                                 end = strstr (cur, "}%");
480                                 if (!end) {
481                                         /* Invalid string */
482                                         continue;
483                                 }
484                                 format = g_strndup (cur, end - cur);
485                                 cur = end + 1;
486                         } else {
487                                 cur++;
488                         }
489
490                         dup_replace = empathy_time_to_string_local (timestamp,
491                                 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
492                         replace = dup_replace;
493                         g_free (format);
494                 } else {
495                         escape_and_append_len (string, cur, 1);
496                         continue;
497                 }
498
499                 /* Here we have a replacement to make */
500                 escape_and_append_len (string, replace, -1);
501                 g_free (dup_replace);
502         }
503         g_string_append (string, "\")");
504
505         script = g_string_free (string, FALSE);
506         webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
507         g_free (script);
508 }
509
510 static void
511 theme_adium_append_message (EmpathyChatView *view,
512                             EmpathyMessage  *msg)
513 {
514         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
515         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
516         EmpathyContact        *sender;
517         EmpathyAccount        *account;
518         gchar                 *dup_body = NULL;
519         const gchar           *body;
520         const gchar           *name;
521         const gchar           *contact_id;
522         EmpathyAvatar         *avatar;
523         const gchar           *avatar_filename = NULL;
524         time_t                 timestamp;
525         gchar                 *html = NULL;
526         gsize                  len = 0;
527         const gchar           *func;
528         const gchar           *service_name;
529         GString               *message_classes = NULL;
530         gboolean              is_backlog;
531
532         if (!priv->page_loaded) {
533                 priv->message_queue = g_list_prepend (priv->message_queue,
534                                                       g_object_ref (msg));
535                 return;
536         }
537
538         /* Get information */
539         sender = empathy_message_get_sender (msg);
540         account = empathy_contact_get_account (sender);
541         service_name = empathy_account_get_protocol (account);
542         timestamp = empathy_message_get_timestamp (msg);
543         body = empathy_message_get_body (msg);
544         dup_body = theme_adium_parse_body (theme, body);
545         if (dup_body) {
546                 body = dup_body;
547         }
548         name = empathy_contact_get_name (sender);
549         contact_id = empathy_contact_get_id (sender);
550
551         /* If this is a /me, append an event */
552         if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
553                 gchar *str;
554
555                 str = g_strdup_printf ("%s %s", name, body);
556                 empathy_chat_view_append_event (view, str);
557                 g_free (str);
558                 g_free (dup_body);
559                 return;
560         }
561
562         /* Get the avatar filename, or a fallback */
563         avatar = empathy_contact_get_avatar (sender);
564         if (avatar) {
565                 avatar_filename = avatar->filename;
566         }
567         if (!avatar_filename) {
568                 if (empathy_contact_is_user (sender)) {
569                         avatar_filename = priv->data->default_outgoing_avatar_filename;
570                 } else {
571                         avatar_filename = priv->data->default_incoming_avatar_filename;
572                 }
573                 if (!avatar_filename) {
574                         if (!priv->data->default_avatar_filename) {
575                                 priv->data->default_avatar_filename =
576                                         empathy_filename_from_icon_name ("stock_person",
577                                                                          GTK_ICON_SIZE_DIALOG);
578                         }
579                         avatar_filename = priv->data->default_avatar_filename;
580                 }
581         }
582
583         is_backlog = empathy_message_is_backlog (msg);
584
585         /* Get the right html/func to add the message */
586         func = "appendMessage";
587
588         message_classes = g_string_new ("message");
589
590         /* eventually append the "history" class */
591         if (is_backlog) {
592                 g_string_append (message_classes, " history");
593         }
594
595         /* check the sender of the message and append the appropriate class */
596         if (empathy_contact_is_user (sender)) {
597                 g_string_append (message_classes, " outgoing");
598         }
599         else {
600                 g_string_append (message_classes, " incoming");
601         }
602
603         /*
604          * To mimick Adium's behavior, we only want to join messages
605          * sent by the same contact within a 5 minute time frame.
606          */
607         if (empathy_contact_equal (priv->last_contact, sender) &&
608             (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
609             (is_backlog == priv->last_is_backlog)) {
610                 /* the messages can be appended */
611                 func = "appendNextMessage";
612                 g_string_append (message_classes, " consecutive");
613
614                 /* check who is the sender of the message to use the correct html file */
615                 if (empathy_contact_is_user (sender)) {
616                         /* check if this is a backlog message and use NextContext.html */
617                         if (is_backlog) {
618                                 html = priv->data->out_nextcontext_html;
619                                 len = priv->data->out_nextcontext_len;
620                         }
621
622                         /*
623                          * html is null if this is not a backlog message or
624                          * if we have to fallback (NextContext.html missing).
625                          * use NextContent.html
626                          */
627                         if (html == NULL) {
628                                 html = priv->data->out_nextcontent_html;
629                                 len = priv->data->out_nextcontent_len;
630                         }
631                 }
632                 else {
633                         if (is_backlog) {
634                                 html = priv->data->in_nextcontext_html;
635                                 len = priv->data->in_nextcontext_len;
636                         }
637
638                         if (html == NULL) {
639                                 html = priv->data->in_nextcontent_html;
640                                 len = priv->data->in_nextcontent_len;
641                         }
642                 }
643         }
644
645         /*
646          * we have html == NULL here if:
647          * 1. the message didn't have to be appended because
648          *    the sender was different or the timestamp was too far
649          * 2. NextContent.html file does not exist, so we must
650          *    not forget to fallback to the correct Content.html
651          */
652         if (html == NULL) {
653                 if (empathy_contact_is_user (sender)) {
654                         if (is_backlog) {
655                                 html = priv->data->out_context_html;
656                                 len = priv->data->out_context_len;
657                         }
658
659                         if (html == NULL) {
660                                 html = priv->data->out_content_html;
661                                 len = priv->data->out_content_len;
662                         }
663                 }
664                 else {
665                         if (is_backlog) {
666                                 html = priv->data->in_context_html;
667                                 len = priv->data->in_context_len;
668                         }
669
670                         if (html == NULL) {
671                                 html = priv->data->in_content_html;
672                                 len = priv->data->in_content_len;
673                         }
674                 }
675         }
676
677         theme_adium_append_html (theme, func, html, len, body, avatar_filename,
678                                  name, contact_id, service_name, message_classes->str,
679                                  timestamp);
680
681         /* Keep the sender of the last displayed message */
682         if (priv->last_contact) {
683                 g_object_unref (priv->last_contact);
684         }
685         priv->last_contact = g_object_ref (sender);
686         priv->last_timestamp = timestamp;
687         priv->last_is_backlog = is_backlog;
688
689         g_free (dup_body);
690         g_string_free (message_classes, TRUE);
691 }
692
693 static void
694 theme_adium_append_event (EmpathyChatView *view,
695                           const gchar     *str)
696 {
697         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
698         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
699
700         if (priv->data->status_html) {
701                 theme_adium_append_html (theme, "appendMessage",
702                                          priv->data->status_html,
703                                          priv->data->status_len,
704                                          str, NULL, NULL, NULL, NULL, "event",
705                                          empathy_time_get_current ());
706         }
707
708         /* There is no last contact */
709         if (priv->last_contact) {
710                 g_object_unref (priv->last_contact);
711                 priv->last_contact = NULL;
712         }
713 }
714
715 static void
716 theme_adium_scroll (EmpathyChatView *view,
717                     gboolean         allow_scrolling)
718 {
719         /* FIXME: Is it possible? I guess we need a js function, but I don't
720          * see any... */
721 }
722
723 static void
724 theme_adium_scroll_down (EmpathyChatView *view)
725 {
726         webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "scrollToBottom()");
727 }
728
729 static gboolean
730 theme_adium_get_has_selection (EmpathyChatView *view)
731 {
732         return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
733 }
734
735 static void
736 theme_adium_clear (EmpathyChatView *view)
737 {
738         EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
739         gchar *basedir_uri;
740
741         priv->page_loaded = FALSE;
742         basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
743         webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
744                                           priv->data->template_html,
745                                           basedir_uri);
746         g_free (basedir_uri);
747
748         /* Clear last contact to avoid trying to add a 'joined'
749          * message when we don't have an insertion point. */
750         if (priv->last_contact) {
751                 g_object_unref (priv->last_contact);
752                 priv->last_contact = NULL;
753         }
754 }
755
756 static gboolean
757 theme_adium_find_previous (EmpathyChatView *view,
758                            const gchar     *search_criteria,
759                            gboolean         new_search)
760 {
761         return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
762                                             search_criteria, FALSE,
763                                             FALSE, TRUE);
764 }
765
766 static gboolean
767 theme_adium_find_next (EmpathyChatView *view,
768                        const gchar     *search_criteria,
769                        gboolean         new_search)
770 {
771         return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
772                                             search_criteria, FALSE,
773                                             TRUE, TRUE);
774 }
775
776 static void
777 theme_adium_find_abilities (EmpathyChatView *view,
778                             const gchar    *search_criteria,
779                             gboolean       *can_do_previous,
780                             gboolean       *can_do_next)
781 {
782         /* FIXME: Does webkit provide an API for that? We have wrap=true in
783          * find_next and find_previous to work around this problem. */
784         if (can_do_previous)
785                 *can_do_previous = TRUE;
786         if (can_do_next)
787                 *can_do_next = TRUE;
788 }
789
790 static void
791 theme_adium_highlight (EmpathyChatView *view,
792                        const gchar     *text)
793 {
794         webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
795         webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
796                                            text, FALSE, 0);
797         webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
798                                                     TRUE);
799 }
800
801 static void
802 theme_adium_copy_clipboard (EmpathyChatView *view)
803 {
804         webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
805 }
806
807 static void
808 theme_adium_iface_init (EmpathyChatViewIface *iface)
809 {
810         iface->append_message = theme_adium_append_message;
811         iface->append_event = theme_adium_append_event;
812         iface->scroll = theme_adium_scroll;
813         iface->scroll_down = theme_adium_scroll_down;
814         iface->get_has_selection = theme_adium_get_has_selection;
815         iface->clear = theme_adium_clear;
816         iface->find_previous = theme_adium_find_previous;
817         iface->find_next = theme_adium_find_next;
818         iface->find_abilities = theme_adium_find_abilities;
819         iface->highlight = theme_adium_highlight;
820         iface->copy_clipboard = theme_adium_copy_clipboard;
821 }
822
823 static void
824 theme_adium_load_finished_cb (WebKitWebView  *view,
825                               WebKitWebFrame *frame,
826                               gpointer        user_data)
827 {
828         EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
829         EmpathyChatView       *chat_view = EMPATHY_CHAT_VIEW (view);
830
831         DEBUG ("Page loaded");
832         priv->page_loaded = TRUE;
833
834         /* Display queued messages */
835         priv->message_queue = g_list_reverse (priv->message_queue);
836         while (priv->message_queue) {
837                 EmpathyMessage *message = priv->message_queue->data;
838
839                 theme_adium_append_message (chat_view, message);
840                 priv->message_queue = g_list_remove (priv->message_queue, message);
841                 g_object_unref (message);
842         }
843 }
844
845 static void
846 theme_adium_finalize (GObject *object)
847 {
848         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
849
850         empathy_adium_data_unref (priv->data);
851         g_free (priv->hovered_uri);
852
853         empathy_conf_notify_remove (empathy_conf_get (),
854                                     priv->notify_enable_webkit_developer_tools_id);
855
856         G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
857 }
858
859 static void
860 theme_adium_dispose (GObject *object)
861 {
862         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
863
864         if (priv->smiley_manager) {
865                 g_object_unref (priv->smiley_manager);
866                 priv->smiley_manager = NULL;
867         }
868
869         if (priv->last_contact) {
870                 g_object_unref (priv->last_contact);
871                 priv->last_contact = NULL;
872         }
873
874         if (priv->inspector_window) {
875                 gtk_widget_destroy (priv->inspector_window);
876                 priv->inspector_window = NULL;
877         }
878
879         G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
880 }
881
882 static gboolean
883 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
884                                       gpointer            user_data)
885 {
886         EmpathyThemeAdium     *theme = user_data;
887         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
888
889         gtk_widget_show_all (priv->inspector_window);
890
891         return TRUE;
892 }
893
894 static gboolean
895 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
896                                        gpointer            user_data)
897 {
898         EmpathyThemeAdium     *theme = user_data;
899         EmpathyThemeAdiumPriv *priv;
900
901         /* We may be called too late - when the theme has already been
902          * destroyed */
903         if (!theme)
904                 return FALSE;
905
906         priv = GET_PRIV (theme);
907
908         if (priv->inspector_window) {
909                 gtk_widget_hide (priv->inspector_window);
910         }
911
912         return TRUE;
913 }
914
915 static WebKitWebView *
916 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
917                                  WebKitWebView *web_view,
918                                  gpointer data)
919 {
920         EmpathyThemeAdiumPriv *priv = GET_PRIV (EMPATHY_THEME_ADIUM (web_view));
921         GtkWidget             *scrolled_window;
922         GtkWidget             *inspector_web_view;
923
924         if (!priv->inspector_window) {
925                 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
926                 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
927                                              800, 600);
928
929                 g_signal_connect (priv->inspector_window, "delete-event",
930                                   G_CALLBACK (gtk_widget_hide_on_delete), NULL);
931
932                 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
933                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
934                 gtk_container_add (GTK_CONTAINER (priv->inspector_window), scrolled_window);
935
936                 inspector_web_view = webkit_web_view_new ();
937                 gtk_container_add (GTK_CONTAINER (scrolled_window), inspector_web_view);
938
939                 return WEBKIT_WEB_VIEW (inspector_web_view);
940         }
941
942         return NULL;
943 }
944
945 static void
946 theme_adium_constructed (GObject *object)
947 {
948         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
949         gchar                 *basedir_uri;
950         const gchar           *font_family = NULL;
951         gint                   font_size = 0;
952         WebKitWebSettings     *webkit_settings;
953
954         /* Set default settings */
955         font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
956         font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
957         webkit_settings = webkit_web_settings_new ();
958         if (font_family) {
959                 g_object_set (G_OBJECT (webkit_settings), "default-font-family", font_family, NULL);
960         }
961         if (font_size) {
962                 g_object_set (G_OBJECT (webkit_settings), "default-font-size", font_size, NULL);
963         }
964
965         g_signal_connect (webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (object)), "inspect-web-view",
966                           G_CALLBACK (theme_adium_inspect_web_view_cb), NULL);
967
968         g_signal_connect (webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (object)), "show-window",
969                           G_CALLBACK (theme_adium_inspector_show_window_cb), object);
970
971         g_signal_connect (webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (object)), "close-window",
972                           G_CALLBACK (theme_adium_inspector_close_window_cb), NULL);
973
974         /* Load template */
975         basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
976         webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
977                                           priv->data->template_html,
978                                           basedir_uri);
979
980         g_object_unref (webkit_settings);
981         g_free (basedir_uri);
982 }
983
984 static void
985 theme_adium_get_property (GObject    *object,
986                           guint       param_id,
987                           GValue     *value,
988                           GParamSpec *pspec)
989 {
990         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
991
992         switch (param_id) {
993         case PROP_ADIUM_DATA:
994                 g_value_set_boxed (value, priv->data);
995                 break;
996         default:
997                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
998                 break;
999         };
1000 }
1001
1002 static void
1003 theme_adium_set_property (GObject      *object,
1004                           guint         param_id,
1005                           const GValue *value,
1006                           GParamSpec   *pspec)
1007 {
1008         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1009
1010         switch (param_id) {
1011         case PROP_ADIUM_DATA:
1012                 g_assert (priv->data == NULL);
1013                 priv->data = g_value_dup_boxed (value);
1014                 break;
1015         default:
1016                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1017                 break;
1018         };
1019 }
1020
1021 static void
1022 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1023 {
1024         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1025
1026         object_class->finalize = theme_adium_finalize;
1027         object_class->dispose = theme_adium_dispose;
1028         object_class->constructed = theme_adium_constructed;
1029         object_class->get_property = theme_adium_get_property;
1030         object_class->set_property = theme_adium_set_property;
1031
1032         g_object_class_install_property (object_class,
1033                                          PROP_ADIUM_DATA,
1034                                          g_param_spec_boxed ("adium-data",
1035                                                              "The theme data",
1036                                                              "Data for the adium theme",
1037                                                               EMPATHY_TYPE_ADIUM_DATA,
1038                                                               G_PARAM_CONSTRUCT_ONLY |
1039                                                               G_PARAM_READWRITE |
1040                                                               G_PARAM_STATIC_STRINGS));
1041
1042         g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1043 }
1044
1045 static void
1046 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1047 {
1048         EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1049                 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1050
1051         theme->priv = priv;
1052
1053         priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1054
1055         g_signal_connect (theme, "load-finished",
1056                           G_CALLBACK (theme_adium_load_finished_cb),
1057                           NULL);
1058         g_signal_connect (theme, "navigation-requested",
1059                           G_CALLBACK (theme_adium_navigation_requested_cb),
1060                           NULL);
1061         g_signal_connect (theme, "populate-popup",
1062                           G_CALLBACK (theme_adium_populate_popup_cb),
1063                           NULL);
1064         g_signal_connect (theme, "hovering-over-link",
1065                           G_CALLBACK (theme_adium_hovering_over_link_cb),
1066                           NULL);
1067
1068         priv->notify_enable_webkit_developer_tools_id =
1069                 empathy_conf_notify_add (empathy_conf_get (),
1070                                          "/apps/empathy/conversation/enable_webkit_developer_tools",
1071                                          theme_adium_notify_enable_webkit_developer_tools_cb,
1072                                          theme);
1073
1074         theme_adium_update_enable_webkit_developer_tools (theme);
1075 }
1076
1077 EmpathyThemeAdium *
1078 empathy_theme_adium_new (EmpathyAdiumData *data)
1079 {
1080         g_return_val_if_fail (data != NULL, NULL);
1081
1082         return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1083                              "adium-data", data,
1084                              NULL);
1085 }
1086
1087 gboolean
1088 empathy_adium_path_is_valid (const gchar *path)
1089 {
1090         gboolean ret;
1091         gchar   *file;
1092
1093         /* The theme is not valid if there is no Info.plist */
1094         file = g_build_filename (path, "Contents", "Info.plist",
1095                                  NULL);
1096         ret = g_file_test (file, G_FILE_TEST_EXISTS);
1097         g_free (file);
1098
1099         if (ret == FALSE)
1100                 return ret;
1101
1102         /* We ship a default Template.html as fallback if there is any problem
1103          * with the one inside the theme. The only other required file is
1104          * Content.html for incoming messages (outgoing fallback to use
1105          * incoming). */
1106         file = g_build_filename (path, "Contents", "Resources", "Incoming",
1107                                  "Content.html", NULL);
1108         ret = g_file_test (file, G_FILE_TEST_EXISTS);
1109         g_free (file);
1110
1111         return ret;
1112 }
1113
1114 GHashTable *
1115 empathy_adium_info_new (const gchar *path)
1116 {
1117         gchar *file;
1118         GValue *value;
1119         GHashTable *info = NULL;
1120
1121         g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1122
1123         file = g_build_filename (path, "Contents", "Info.plist", NULL);
1124         value = empathy_plist_parse_from_file (file);
1125         g_free (file);
1126
1127         if (value == NULL)
1128                 return NULL;
1129
1130         info = g_value_dup_boxed (value);
1131         tp_g_value_slice_free (value);
1132
1133         /* Insert the theme's path into the hash table,
1134          * keys have to be dupped */
1135         tp_asv_set_string (info, g_strdup ("path"), path);
1136
1137         return info;
1138 }
1139
1140 GType
1141 empathy_adium_data_get_type (void)
1142 {
1143   static GType type_id = 0;
1144
1145   if (!type_id)
1146     {
1147       type_id = g_boxed_type_register_static ("EmpathyAdiumData",
1148           (GBoxedCopyFunc) empathy_adium_data_ref,
1149           (GBoxedFreeFunc) empathy_adium_data_unref);
1150     }
1151
1152   return type_id;
1153 }
1154
1155 EmpathyAdiumData  *
1156 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
1157 {
1158         EmpathyAdiumData *data;
1159         gchar            *file;
1160         gchar            *template_html = NULL;
1161         gsize             template_len;
1162         gchar            *footer_html = NULL;
1163         gsize             footer_len;
1164         GString          *string;
1165         gchar           **strv = NULL;
1166         gchar            *css_path;
1167         guint             len = 0;
1168         guint             i = 0;
1169
1170         g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1171
1172         data = g_slice_new0 (EmpathyAdiumData);
1173         data->ref_count = 1;
1174         data->path = g_strdup (path);
1175         data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
1176                 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
1177         data->info = g_hash_table_ref (info);
1178
1179         /* Load html files */
1180         file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
1181         g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
1182         g_free (file);
1183
1184         file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
1185         g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
1186         g_free (file);
1187
1188         file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
1189         g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
1190         g_free (file);
1191
1192         file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
1193         g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
1194         g_free (file);
1195
1196         file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
1197         g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
1198         g_free (file);
1199
1200         file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
1201         g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
1202         g_free (file);
1203
1204         file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
1205         g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
1206         g_free (file);
1207
1208         file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
1209         g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
1210         g_free (file);
1211
1212         file = g_build_filename (data->basedir, "Status.html", NULL);
1213         g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
1214         g_free (file);
1215
1216         file = g_build_filename (data->basedir, "Footer.html", NULL);
1217         g_file_get_contents (file, &footer_html, &footer_len, NULL);
1218         g_free (file);
1219
1220         file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
1221         if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1222                 data->default_incoming_avatar_filename = file;
1223         } else {
1224                 g_free (file);
1225         }
1226
1227         file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
1228         if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
1229                 data->default_outgoing_avatar_filename = file;
1230         } else {
1231                 g_free (file);
1232         }
1233
1234         css_path = g_build_filename (data->basedir, "main.css", NULL);
1235
1236         /* There is 2 formats for Template.html: The old one has 4 parameters,
1237          * the new one has 5 parameters. */
1238         file = g_build_filename (data->basedir, "Template.html", NULL);
1239         if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
1240                 strv = g_strsplit (template_html, "%@", -1);
1241                 len = g_strv_length (strv);
1242         }
1243         g_free (file);
1244
1245         if (len != 5 && len != 6) {
1246                 /* Either the theme has no template or it don't have the good
1247                  * number of parameters. Fallback to use our own template. */
1248                 g_free (template_html);
1249                 g_strfreev (strv);
1250
1251                 file = empathy_file_lookup ("Template.html", "data");
1252                 g_file_get_contents (file, &template_html, &template_len, NULL);
1253                 g_free (file);
1254                 strv = g_strsplit (template_html, "%@", -1);
1255                 len = g_strv_length (strv);
1256         }
1257
1258         /* Replace %@ with the needed information in the template html. */
1259         string = g_string_sized_new (template_len);
1260         g_string_append (string, strv[i++]);
1261         g_string_append (string, data->basedir);
1262         g_string_append (string, strv[i++]);
1263         if (len == 6) {
1264                 const gchar *variant;
1265
1266                 /* We include main.css by default */
1267                 g_string_append_printf (string, "@import url(\"%s\");", css_path);
1268                 g_string_append (string, strv[i++]);
1269                 variant = tp_asv_get_string (data->info, "DefaultVariant");
1270                 if (variant) {
1271                         g_string_append (string, "Variants/");
1272                         g_string_append (string, variant);
1273                         g_string_append (string, ".css");
1274                 }
1275         } else {
1276                 /* FIXME: We should set main.css OR the variant css */
1277                 g_string_append (string, css_path);
1278         }
1279         g_string_append (string, strv[i++]);
1280         g_string_append (string, ""); /* We don't want header */
1281         g_string_append (string, strv[i++]);
1282         /* FIXME: We should replace adium %macros% in footer */
1283         if (footer_html) {
1284                 g_string_append (string, footer_html);
1285         }
1286         g_string_append (string, strv[i++]);
1287         data->template_html = g_string_free (string, FALSE);
1288
1289         g_free (footer_html);
1290         g_free (template_html);
1291         g_free (css_path);
1292         g_strfreev (strv);
1293
1294         return data;
1295 }
1296
1297 EmpathyAdiumData  *
1298 empathy_adium_data_new (const gchar *path)
1299 {
1300         EmpathyAdiumData *data;
1301         GHashTable *info;
1302
1303         info = empathy_adium_info_new (path);
1304         data = empathy_adium_data_new_with_info (path, info);
1305         g_hash_table_unref (info);
1306
1307         return data;
1308 }
1309
1310 EmpathyAdiumData  *
1311 empathy_adium_data_ref (EmpathyAdiumData *data)
1312 {
1313         g_return_val_if_fail (data != NULL, NULL);
1314
1315         g_atomic_int_inc (&data->ref_count);
1316
1317         return data;
1318 }
1319
1320 void
1321 empathy_adium_data_unref (EmpathyAdiumData *data)
1322 {
1323         g_return_if_fail (data != NULL);
1324
1325         if (g_atomic_int_dec_and_test (&data->ref_count)) {
1326                 g_free (data->path);
1327                 g_free (data->basedir);
1328                 g_free (data->template_html);
1329                 g_free (data->in_content_html);
1330                 g_free (data->in_nextcontent_html);
1331                 g_free (data->in_context_html);
1332                 g_free (data->in_nextcontext_html);
1333                 g_free (data->out_content_html);
1334                 g_free (data->out_nextcontent_html);
1335                 g_free (data->out_context_html);
1336                 g_free (data->out_nextcontext_html);
1337                 g_free (data->default_avatar_filename);
1338                 g_free (data->default_incoming_avatar_filename);
1339                 g_free (data->default_outgoing_avatar_filename);
1340                 g_free (data->status_html);
1341                 g_hash_table_unref (data->info);
1342                 g_slice_free (EmpathyAdiumData, data);
1343         }
1344 }
1345
1346 GHashTable *
1347 empathy_adium_data_get_info (EmpathyAdiumData *data)
1348 {
1349         g_return_val_if_fail (data != NULL, NULL);
1350
1351         return data->info;
1352 }
1353
1354 const gchar *
1355 empathy_adium_data_get_path (EmpathyAdiumData *data)
1356 {
1357         g_return_val_if_fail (data != NULL, NULL);
1358
1359         return data->path;
1360 }
1361