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