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