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