]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-theme-adium.c
Support fallback avatars provided by adium themes
[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
29 #include <libempathy/empathy-time.h>
30 #include <libempathy/empathy-utils.h>
31
32 #include "empathy-theme-adium.h"
33 #include "empathy-smiley-manager.h"
34 #include "empathy-conf.h"
35 #include "empathy-ui-utils.h"
36
37 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
38 #include <libempathy/empathy-debug.h>
39
40 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
41
42 /* "Join" consecutive messages with timestamps within five minutes */
43 #define MESSAGE_JOIN_PERIOD 5*60
44
45 typedef struct {
46         EmpathySmileyManager *smiley_manager;
47         EmpathyContact       *last_contact;
48         time_t                last_timestamp;
49         gboolean              page_loaded;
50         GList                *message_queue;
51         gchar                *path;
52         gchar                *default_avatar_filename;
53         gchar                *default_incoming_avatar_filename;
54         gchar                *default_outgoing_avatar_filename;
55         gchar                *template_html;
56         gchar                *basedir;
57         gchar                *in_content_html;
58         gsize                 in_content_len;
59         gchar                *in_nextcontent_html;
60         gsize                 in_nextcontent_len;
61         gchar                *out_content_html;
62         gsize                 out_content_len;
63         gchar                *out_nextcontent_html;
64         gsize                 out_nextcontent_len;
65         gchar                *status_html;
66         gsize                 status_len;
67 } EmpathyThemeAdiumPriv;
68
69 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
70
71 enum {
72         PROP_0,
73         PROP_PATH,
74 };
75
76 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
77                          WEBKIT_TYPE_WEB_VIEW,
78                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
79                                                 theme_adium_iface_init));
80
81 static void
82 theme_adium_load (EmpathyThemeAdium *theme)
83 {
84         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
85         gchar                 *file;
86         gchar                 *template_html = NULL;
87         gsize                  template_len;
88         GString               *string;
89         gchar                **strv = NULL;
90         gchar                 *css_path;
91         guint                  len = 0;
92         guint                  i = 0;
93         gchar                 *basedir_uri;
94
95         priv->basedir = g_strconcat (priv->path, G_DIR_SEPARATOR_S "Contents" G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
96         basedir_uri = g_strconcat ("file://", priv->basedir, NULL);
97
98         /* Load html files */
99         file = g_build_filename (priv->basedir, "Incoming", "Content.html", NULL);
100         g_file_get_contents (file, &priv->in_content_html, &priv->in_content_len, NULL);
101         g_free (file);
102
103         file = g_build_filename (priv->basedir, "Incoming", "NextContent.html", NULL);
104         g_file_get_contents (file, &priv->in_nextcontent_html, &priv->in_nextcontent_len, NULL);
105         g_free (file);
106
107         file = g_build_filename (priv->basedir, "Outgoing", "Content.html", NULL);
108         g_file_get_contents (file, &priv->out_content_html, &priv->out_content_len, NULL);
109         g_free (file);
110
111         file = g_build_filename (priv->basedir, "Outgoing", "NextContent.html", NULL);
112         g_file_get_contents (file, &priv->out_nextcontent_html, &priv->out_nextcontent_len, NULL);
113         g_free (file);
114
115         file = g_build_filename (priv->basedir, "Status.html", NULL);
116         g_file_get_contents (file, &priv->status_html, &priv->status_len, NULL);
117         g_free (file);
118
119         file = g_build_filename (priv->basedir, "Incoming", "buddy_icon.png", NULL);
120         if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
121                 priv->default_incoming_avatar_filename = file;
122         } else {
123                 g_free (file);
124         }
125
126         file = g_build_filename (priv->basedir, "Outgoing", "buddy_icon.png", NULL);
127         if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
128                 priv->default_outgoing_avatar_filename = file;
129         } else {
130                 g_free (file);
131         }
132
133         css_path = g_build_filename (priv->basedir, "main.css", NULL);
134
135         /* There is 2 formats for Template.html: The old one has 4 parameters,
136          * the new one has 5 parameters. */
137         file = g_build_filename (priv->basedir, "Template.html", NULL);
138         if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
139                 strv = g_strsplit (template_html, "%@", -1);
140                 len = g_strv_length (strv);
141         }
142         g_free (file);
143
144         if (len != 5 && len != 6) {
145                 /* Either the theme has no template or it don't have the good
146                  * number of parameters. Fallback to use our own template. */
147                 g_free (template_html);
148                 g_strfreev (strv);
149
150                 file = empathy_file_lookup ("Template.html", "data");
151                 g_file_get_contents (file, &template_html, &template_len, NULL);
152                 g_free (file);
153                 strv = g_strsplit (template_html, "%@", -1);
154                 len = g_strv_length (strv);
155         }
156
157         /* Replace %@ with the needed information in the template html. */
158         string = g_string_sized_new (template_len);
159         g_string_append (string, strv[i++]);
160         g_string_append (string, priv->basedir);
161         g_string_append (string, strv[i++]);
162         if (len == 6) {
163                 /* We include main.css by default */
164                 g_string_append_printf (string, "@import url(\"%s\");", css_path);
165                 g_string_append (string, strv[i++]);
166                 /* FIXME: We should set the variant css here */
167                 g_string_append (string, "");
168         } else {
169                 /* FIXME: We should set main.css OR the variant css */
170                 g_string_append (string, css_path);
171         }
172         g_string_append (string, strv[i++]);
173         g_string_append (string, ""); /* We don't want header */
174         g_string_append (string, strv[i++]);
175         g_string_append (string, ""); /* FIXME: We don't support footer yet */
176         g_string_append (string, strv[i++]);
177         priv->template_html = g_string_free (string, FALSE);
178
179         /* Load the template */
180         webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (theme),
181                                           priv->template_html, basedir_uri);
182
183         g_free (basedir_uri);
184         g_free (template_html);
185         g_free (css_path);
186         g_strfreev (strv);
187 }
188
189 static WebKitNavigationResponse
190 theme_adium_navigation_requested_cb (WebKitWebView        *view,
191                                      WebKitWebFrame       *frame,
192                                      WebKitNetworkRequest *request,
193                                      gpointer              user_data)
194 {
195         const gchar *uri;
196
197         uri = webkit_network_request_get_uri (request);
198         empathy_url_show (GTK_WIDGET (view), uri);
199
200         return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
201 }
202
203 static void
204 theme_adium_populate_popup_cb (WebKitWebView *view,
205                                GtkMenu       *menu,
206                                gpointer       user_data)
207 {
208         GtkWidget *item;
209
210         /* Remove default menu items */
211         gtk_container_foreach (GTK_CONTAINER (menu),
212                 (GtkCallback) gtk_widget_destroy, NULL);
213         
214         /* Select all item */
215         item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
216         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
217         gtk_widget_show (item);
218                 
219         g_signal_connect_swapped (item, "activate",
220                                   G_CALLBACK (webkit_web_view_select_all),
221                                   view);
222
223         /* Copy menu item */
224         if (webkit_web_view_can_copy_clipboard (view)) {
225                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
226                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
227                 gtk_widget_show (item);
228                 
229                 g_signal_connect_swapped (item, "activate",
230                                           G_CALLBACK (webkit_web_view_copy_clipboard),
231                                           view);
232         }
233
234         /* Clear menu item */
235         item = gtk_separator_menu_item_new ();
236         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
237         gtk_widget_show (item);
238                 
239         item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
240         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
241         gtk_widget_show (item);
242                 
243         g_signal_connect_swapped (item, "activate",
244                                   G_CALLBACK (empathy_chat_view_clear),
245                                   view);
246
247         /* FIXME: Add open_link and copy_link when those bugs are fixed:
248          * https://bugs.webkit.org/show_bug.cgi?id=16092
249          * https://bugs.webkit.org/show_bug.cgi?id=16562
250          */
251 }
252
253 static gchar *
254 theme_adium_parse_body (EmpathyThemeAdium *theme,
255                         const gchar       *text)
256 {
257         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
258         gboolean               use_smileys = FALSE;
259         GSList                *smileys, *l;
260         GString               *string;
261         gint                   i;
262         GRegex                *uri_regex;
263         GMatchInfo            *match_info;
264         gboolean               match;
265         gchar                 *ret = NULL;
266         gint                   prev;
267
268         empathy_conf_get_bool (empathy_conf_get (),
269                                EMPATHY_PREFS_CHAT_SHOW_SMILEYS,
270                                &use_smileys);
271
272         if (use_smileys) {
273                 /* Replace smileys by a <img/> tag */
274                 string = g_string_sized_new (strlen (text));
275                 smileys = empathy_smiley_manager_parse (priv->smiley_manager, text);
276                 for (l = smileys; l; l = l->next) {
277                         EmpathySmiley *smiley;
278
279                         smiley = l->data;
280                         if (smiley->path) {
281                                 g_string_append_printf (string,
282                                                         "<abbr title='%s'><img src=\"%s\"/ alt=\"%s\"/></abbr>",
283                                                         smiley->str, smiley->path, smiley->str);
284                         } else {
285                                 gchar *str;
286
287                                 str = g_markup_escape_text (smiley->str, -1);
288                                 g_string_append (string, str);
289                                 g_free (str);
290                         }
291                         empathy_smiley_free (smiley);
292                 }
293                 g_slist_free (smileys);
294
295                 g_free (ret);
296                 text = ret = g_string_free (string, FALSE);
297         }
298
299         /* Add <a href></a> arround links */
300         uri_regex = empathy_uri_regex_dup_singleton ();
301         match = g_regex_match (uri_regex, text, 0, &match_info);
302         if (match) {
303                 gint last = 0;
304                 gint s = 0, e = 0;
305
306                 string = g_string_sized_new (strlen (text));
307                 do {
308                         g_match_info_fetch_pos (match_info, 0, &s, &e);
309
310                         if (s > last) {
311                                 /* Append the text between last link (or the
312                                  * start of the message) and this link */
313                                 g_string_append_len (string, text + last, s - last);
314                         }
315
316                         /* Append the link inside <a href=""></a> tag */
317                         g_string_append (string, "<a href=\"");
318                         g_string_append_len (string, text + s, e - s);
319                         g_string_append (string, "\">");
320                         g_string_append_len (string, text + s, e - s);
321                         g_string_append (string, "</a>");
322
323                         last = e;
324                 } while (g_match_info_next (match_info, NULL));
325
326                 if (e < strlen (text)) {
327                         /* Append the text after the last link */
328                         g_string_append_len (string, text + e, strlen (text) - e);
329                 }
330
331                 g_free (ret);
332                 text = ret = g_string_free (string, FALSE);
333         }
334         g_match_info_free (match_info);
335         g_regex_unref (uri_regex);
336
337         /* Replace \n by <br/> */
338         string = NULL;
339         prev = 0;
340         for (i = 0; text[i] != '\0'; i++) {
341                 if (text[i] == '\n') {
342                         if (!string ) {
343                                 string = g_string_sized_new (strlen (text));
344                         }
345                         g_string_append_len (string, text + prev, i - prev);
346                         g_string_append (string, "<br/>");
347                         prev = i + 1;
348                 }
349         }
350         if (string) {
351                 g_string_append (string, text + prev);
352                 g_free (ret);
353                 text = ret = g_string_free (string, FALSE);
354         }
355
356         return ret;
357 }
358
359 static void
360 escape_and_append_len (GString *string, const gchar *str, gint len)
361 {
362         while (*str != '\0' && len != 0) {
363                 switch (*str) {
364                 case '\\':
365                         /* \ becomes \\ */
366                         g_string_append (string, "\\\\");       
367                         break;
368                 case '\"':
369                         /* " becomes \" */
370                         g_string_append (string, "\\\"");
371                         break;
372                 case '\n':
373                         /* Remove end of lines */
374                         break;
375                 default:
376                         g_string_append_c (string, *str);
377                 }
378
379                 str++;
380                 len--;
381         }
382 }
383
384 static gboolean
385 theme_adium_match (const gchar **str, const gchar *match)
386 {
387         gint len;
388
389         len = strlen (match);
390         if (strncmp (*str, match, len) == 0) {
391                 *str += len - 1;
392                 return TRUE;
393         }
394
395         return FALSE;
396 }
397
398 static void
399 theme_adium_append_html (EmpathyThemeAdium *theme,
400                          const gchar       *func,
401                          const gchar       *html, gsize len,
402                          const gchar       *message,
403                          const gchar       *avatar_filename,
404                          const gchar       *name,
405                          time_t             timestamp)
406 {
407         GString     *string;
408         const gchar *cur = NULL;
409         gchar       *script;
410
411         /* Make some search-and-replace in the html code */
412         string = g_string_sized_new (len + strlen (message));
413         g_string_append_printf (string, "%s(\"", func);
414         for (cur = html; *cur != '\0'; cur++) {
415                 const gchar *replace = NULL;
416                 gchar       *dup_replace = NULL;
417
418                 if (theme_adium_match (&cur, "%message%")) {
419                         replace = message;
420                 } else if (theme_adium_match (&cur, "%userIconPath%")) {
421                         replace = avatar_filename;
422                 } else if (theme_adium_match (&cur, "%sender%")) {
423                         replace = name;
424                 } else if (theme_adium_match (&cur, "%time")) {
425                         gchar *format = NULL;
426                         gchar *end;
427
428                         /* Time can be in 2 formats:
429                          * %time% or %time{strftime format}%
430                          * Extract the time format if provided. */
431                         if (cur[1] == '{') {
432                                 cur += 2;
433                                 end = strstr (cur, "}%");
434                                 if (!end) {
435                                         /* Invalid string */
436                                         continue;
437                                 }
438                                 format = g_strndup (cur, end - cur);
439                                 cur = end + 1;
440                         } else {
441                                 cur++;
442                         }
443
444                         dup_replace = empathy_time_to_string_local (timestamp,
445                                 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
446                         replace = dup_replace;
447                         g_free (format);
448                 } else {
449                         escape_and_append_len (string, cur, 1);
450                         continue;
451                 }
452
453                 /* Here we have a replacement to make */
454                 escape_and_append_len (string, replace, -1);
455                 g_free (dup_replace);
456         }
457         g_string_append (string, "\")");
458
459         script = g_string_free (string, FALSE);
460         webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
461         g_free (script);
462 }
463
464 static void
465 theme_adium_append_message (EmpathyChatView *view,
466                             EmpathyMessage  *msg)
467 {
468         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
469         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
470         EmpathyContact        *sender;
471         gchar                 *dup_body = NULL;
472         const gchar           *body;
473         const gchar           *name;
474         EmpathyAvatar         *avatar;
475         const gchar           *avatar_filename = NULL;
476         time_t                 timestamp;
477         gchar                 *html = NULL;
478         gsize                  len = 0;
479         const gchar           *func;
480
481         if (!priv->page_loaded) {
482                 priv->message_queue = g_list_prepend (priv->message_queue,
483                                                       g_object_ref (msg));
484                 return;
485         }
486
487         /* Get information */
488         sender = empathy_message_get_sender (msg);
489         timestamp = empathy_message_get_timestamp (msg);
490         body = empathy_message_get_body (msg);
491         dup_body = theme_adium_parse_body (theme, body);
492         if (dup_body) {
493                 body = dup_body;
494         }
495         name = empathy_contact_get_name (sender);
496
497         /* If this is a /me, append an event */
498         if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
499                 gchar *str;
500
501                 str = g_strdup_printf ("%s %s", name, body);
502                 empathy_chat_view_append_event (view, str);
503                 g_free (str);
504                 g_free (dup_body);
505                 return;
506         }
507
508         /* Get the avatar filename, or a fallback */
509         avatar = empathy_contact_get_avatar (sender);
510         if (avatar) {
511                 avatar_filename = avatar->filename;
512         }
513         if (!avatar_filename) {
514                 if (empathy_contact_is_user (sender)) {
515                         avatar_filename = priv->default_outgoing_avatar_filename;
516                 } else {
517                         avatar_filename = priv->default_incoming_avatar_filename;
518                 }
519                 if (!avatar_filename) {
520                         if (!priv->default_avatar_filename) {
521                                 priv->default_avatar_filename =
522                                         empathy_filename_from_icon_name ("stock_person",
523                                                                          GTK_ICON_SIZE_DIALOG);
524                         }
525                         avatar_filename = priv->default_avatar_filename;
526                 }
527         }
528
529         /* Get the right html/func to add the message */
530         func = "appendMessage";
531         /*
532          * To mimick Adium's behavior, we only want to join messages
533          * sent within a 5 minute time frame.
534          */
535         if (empathy_contact_equal (priv->last_contact, sender) &&
536             (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD)) {
537                 func = "appendNextMessage";
538                 if (empathy_contact_is_user (sender)) {
539                         html = priv->out_nextcontent_html;
540                         len = priv->out_nextcontent_len;
541                 }
542                 if (!html) {
543                         html = priv->in_nextcontent_html;
544                         len = priv->in_nextcontent_len;
545                 }
546         }
547         if (!html) {
548                 if (empathy_contact_is_user (sender)) {
549                         html = priv->out_content_html;
550                         len = priv->out_content_len;
551                 }
552                 if (!html) {
553                         html = priv->in_content_html;
554                         len = priv->in_content_len;
555                 }
556         }
557
558         theme_adium_append_html (theme, func, html, len, body, avatar_filename,
559                                  name, timestamp);
560
561         /* Keep the sender of the last displayed message */
562         if (priv->last_contact) {
563                 g_object_unref (priv->last_contact);
564         }
565         priv->last_contact = g_object_ref (sender);
566         priv->last_timestamp = timestamp;
567
568         g_free (dup_body);
569 }
570
571 static void
572 theme_adium_append_event (EmpathyChatView *view,
573                           const gchar     *str)
574 {
575         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
576         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
577
578         if (priv->status_html) {
579                 theme_adium_append_html (theme, "appendMessage",
580                                          priv->status_html, priv->status_len,
581                                          str, NULL, NULL,
582                                          empathy_time_get_current ());
583         }
584
585         /* There is no last contact */
586         if (priv->last_contact) {
587                 g_object_unref (priv->last_contact);
588                 priv->last_contact = NULL;
589         }
590 }
591
592 static void
593 theme_adium_scroll (EmpathyChatView *view,
594                     gboolean         allow_scrolling)
595 {
596         /* FIXME: Is it possible? I guess we need a js function, but I don't
597          * see any... */
598 }
599
600 static void
601 theme_adium_scroll_down (EmpathyChatView *view)
602 {
603         webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "scrollToBottom()");
604 }
605
606 static gboolean
607 theme_adium_get_has_selection (EmpathyChatView *view)
608 {
609         return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
610 }
611
612 static void
613 theme_adium_clear (EmpathyChatView *view)
614 {
615         EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
616         gchar *basedir_uri;
617
618         priv->page_loaded = FALSE;
619         basedir_uri = g_strconcat ("file://", priv->basedir, NULL);
620         webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
621                                           priv->template_html, basedir_uri);
622         g_free (basedir_uri);
623 }
624
625 static gboolean
626 theme_adium_find_previous (EmpathyChatView *view,
627                            const gchar     *search_criteria,
628                            gboolean         new_search)
629 {
630         return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
631                                             search_criteria, FALSE,
632                                             FALSE, TRUE);
633 }
634
635 static gboolean
636 theme_adium_find_next (EmpathyChatView *view,
637                        const gchar     *search_criteria,
638                        gboolean         new_search)
639 {
640         return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
641                                             search_criteria, FALSE,
642                                             TRUE, TRUE);
643 }
644
645 static void
646 theme_adium_find_abilities (EmpathyChatView *view,
647                             const gchar    *search_criteria,
648                             gboolean       *can_do_previous,
649                             gboolean       *can_do_next)
650 {
651         /* FIXME: Does webkit provide an API for that? We have wrap=true in
652          * find_next and find_previous to work around this problem. */
653         if (can_do_previous)
654                 *can_do_previous = TRUE;
655         if (can_do_next)
656                 *can_do_next = TRUE;
657 }
658
659 static void
660 theme_adium_highlight (EmpathyChatView *view,
661                        const gchar     *text)
662 {
663         webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
664         webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
665                                            text, FALSE, 0);
666         webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
667                                                     TRUE);
668 }
669
670 static void
671 theme_adium_copy_clipboard (EmpathyChatView *view)
672 {
673         webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
674 }
675
676 static void
677 theme_adium_iface_init (EmpathyChatViewIface *iface)
678 {
679         iface->append_message = theme_adium_append_message;
680         iface->append_event = theme_adium_append_event;
681         iface->scroll = theme_adium_scroll;
682         iface->scroll_down = theme_adium_scroll_down;
683         iface->get_has_selection = theme_adium_get_has_selection;
684         iface->clear = theme_adium_clear;
685         iface->find_previous = theme_adium_find_previous;
686         iface->find_next = theme_adium_find_next;
687         iface->find_abilities = theme_adium_find_abilities;
688         iface->highlight = theme_adium_highlight;
689         iface->copy_clipboard = theme_adium_copy_clipboard;
690 }
691
692 static void
693 theme_adium_load_finished_cb (WebKitWebView  *view,
694                               WebKitWebFrame *frame,
695                               gpointer        user_data)
696 {
697         EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
698         EmpathyChatView       *chat_view = EMPATHY_CHAT_VIEW (view);
699
700         DEBUG ("Page loaded");
701         priv->page_loaded = TRUE;
702
703         /* Display queued messages */
704         priv->message_queue = g_list_reverse (priv->message_queue);
705         while (priv->message_queue) {
706                 EmpathyMessage *message = priv->message_queue->data;
707
708                 theme_adium_append_message (chat_view, message);
709                 priv->message_queue = g_list_remove (priv->message_queue, message);
710                 g_object_unref (message);
711         }
712 }
713
714 static void
715 theme_adium_finalize (GObject *object)
716 {
717         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
718
719         g_free (priv->basedir);
720         g_free (priv->template_html);
721         g_free (priv->in_content_html);
722         g_free (priv->in_nextcontent_html);
723         g_free (priv->out_content_html);
724         g_free (priv->out_nextcontent_html);
725         g_free (priv->default_avatar_filename);
726         g_free (priv->default_incoming_avatar_filename);
727         g_free (priv->default_outgoing_avatar_filename);
728         g_free (priv->path);
729
730         G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
731 }
732
733 static void
734 theme_adium_dispose (GObject *object)
735 {
736         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
737
738         if (priv->smiley_manager) {
739                 g_object_unref (priv->smiley_manager);
740                 priv->smiley_manager = NULL;
741         }
742
743         if (priv->last_contact) {
744                 g_object_unref (priv->last_contact);
745                 priv->last_contact = NULL;
746         }
747
748         G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
749 }
750
751 static void
752 theme_adium_constructed (GObject *object)
753 {
754         theme_adium_load (EMPATHY_THEME_ADIUM (object));
755 }
756
757 static void
758 theme_adium_get_property (GObject    *object,
759                           guint       param_id,
760                           GValue     *value,
761                           GParamSpec *pspec)
762 {
763         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
764
765         switch (param_id) {
766         case PROP_PATH:
767                 g_value_set_string (value, priv->path);
768                 break;
769         default:
770                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
771                 break;
772         };
773 }
774
775 static void
776 theme_adium_set_property (GObject      *object,
777                           guint         param_id,
778                           const GValue *value,
779                           GParamSpec   *pspec)
780 {
781         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
782
783         switch (param_id) {
784         case PROP_PATH:
785                 g_free (priv->path);
786                 priv->path = g_value_dup_string (value);
787                 break;
788         default:
789                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
790                 break;
791         };
792 }
793
794 static void
795 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
796 {
797         GObjectClass *object_class = G_OBJECT_CLASS (klass);
798         
799         object_class->finalize = theme_adium_finalize;
800         object_class->dispose = theme_adium_dispose;
801         object_class->constructed = theme_adium_constructed;
802         object_class->get_property = theme_adium_get_property;
803         object_class->set_property = theme_adium_set_property;
804
805         g_object_class_install_property (object_class,
806                                          PROP_PATH,
807                                          g_param_spec_string ("path",
808                                                               "The theme path",
809                                                               "Path to the adium theme",
810                                                               g_get_home_dir (),
811                                                               G_PARAM_CONSTRUCT_ONLY |
812                                                               G_PARAM_READWRITE));
813
814
815         g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
816 }
817
818 static void
819 empathy_theme_adium_init (EmpathyThemeAdium *theme)
820 {
821         EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
822                 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
823
824         theme->priv = priv;     
825
826         priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
827
828         g_signal_connect (theme, "load-finished",
829                           G_CALLBACK (theme_adium_load_finished_cb),
830                           NULL);
831         g_signal_connect (theme, "navigation-requested",
832                           G_CALLBACK (theme_adium_navigation_requested_cb),
833                           NULL);
834         g_signal_connect (theme, "populate-popup",
835                           G_CALLBACK (theme_adium_populate_popup_cb),
836                           NULL);
837 }
838
839 EmpathyThemeAdium *
840 empathy_theme_adium_new (const gchar *path)
841 {
842         g_return_val_if_fail (empathy_theme_adium_is_valid (path), NULL);
843
844         return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
845                              "path", path,
846                              NULL);
847 }
848
849 gboolean
850 empathy_theme_adium_is_valid (const gchar *path)
851 {
852         gboolean ret;
853         gchar   *file;
854
855         /* We ship a default Template.html as fallback if there is any problem
856          * with the one inside the theme. The only other required file is
857          * Content.html for incoming messages (outgoing fallback to use
858          * incoming). */
859         file = g_build_filename (path, "Contents", "Resources", "Incoming",
860                                  "Content.html", NULL);
861         ret = g_file_test (file, G_FILE_TEST_EXISTS);
862         g_free (file);
863
864         return ret;
865 }
866