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