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