]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-theme-adium.c
Add UI in the preference dialog to select the path to adium theme
[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 #include <string.h>
24
25 #include <webkit/webkitnetworkrequest.h>
26
27 #include <libempathy/empathy-time.h>
28 #include <libempathy/empathy-utils.h>
29
30 #include "empathy-theme-adium.h"
31 #include "empathy-smiley-manager.h"
32 #include "empathy-conf.h"
33 #include "empathy-ui-utils.h"
34
35 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
36 #include <libempathy/empathy-debug.h>
37
38 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
39
40 typedef struct {
41         EmpathySmileyManager *smiley_manager;
42         EmpathyContact       *last_contact;
43         gboolean              page_loaded;
44         GList                *message_queue;
45         gchar                *default_avatar_filename;
46         gchar                *in_content_html;
47         gsize                 in_content_len;
48         gchar                *in_nextcontent_html;
49         gsize                 in_nextcontent_len;
50         gchar                *out_content_html;
51         gsize                 out_content_len;
52         gchar                *out_nextcontent_html;
53         gsize                 out_nextcontent_len;
54 } EmpathyThemeAdiumPriv;
55
56 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
57
58 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
59                          WEBKIT_TYPE_WEB_VIEW,
60                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
61                                                 theme_adium_iface_init));
62
63 static void
64 theme_adium_load (EmpathyThemeAdium *theme)
65 {
66         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
67         gchar                 *basedir;
68         gchar                 *file;
69         gchar                 *template_html;
70         gsize                  template_len;
71         GString               *string;
72         gchar                **strv;
73         gchar                 *content;
74         gchar                 *css_path;
75
76         /* FIXME: Find a better way to get the theme dir */
77         basedir = g_build_filename (g_get_home_dir (), "Contents", "Resources", NULL);
78
79         /* Load html files */
80         file = g_build_filename (basedir, "Template.html", NULL);
81         g_file_get_contents (file, &template_html, &template_len, NULL);
82         g_free (file);
83
84         file = g_build_filename (basedir, "Incoming", "Content.html", NULL);
85         g_file_get_contents (file, &priv->in_content_html, &priv->in_content_len, NULL);
86         g_free (file);
87
88         file = g_build_filename (basedir, "Incoming", "NextContent.html", NULL);
89         g_file_get_contents (file, &priv->in_nextcontent_html, &priv->in_nextcontent_len, NULL);
90         g_free (file);
91
92         file = g_build_filename (basedir, "Outgoing", "Content.html", NULL);
93         g_file_get_contents (file, &priv->out_content_html, &priv->out_content_len, NULL);
94         g_free (file);
95
96         file = g_build_filename (basedir, "Outgoing", "NextContent.html", NULL);
97         g_file_get_contents (file, &priv->out_nextcontent_html, &priv->out_nextcontent_len, NULL);
98         g_free (file);
99
100         css_path = g_build_filename (basedir, "main.css", NULL);
101
102         /* Replace %@ with the needed information in the template html */
103         strv = g_strsplit (template_html, "%@", 5);
104         string = g_string_sized_new (template_len);
105         g_string_append (string, strv[0]);
106         g_string_append (string, basedir);
107         g_string_append (string, strv[1]);
108         g_string_append (string, css_path);
109         g_string_append (string, strv[2]);
110         g_string_append (string, ""); /* We don't want header */
111         g_string_append (string, strv[3]);
112         g_string_append (string, ""); /* We have no footer */
113         g_string_append (string, strv[4]);
114         content = g_string_free (string, FALSE);
115
116         /* Load the template */
117         webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (theme),
118                                           content, basedir);
119
120         g_free (basedir);
121         g_free (content);
122         g_free (template_html);
123         g_free (css_path);
124         g_strfreev (strv);
125 }
126
127 static WebKitNavigationResponse
128 theme_adium_navigation_requested_cb (WebKitWebView        *view,
129                                      WebKitWebFrame       *frame,
130                                      WebKitNetworkRequest *request,
131                                      gpointer              user_data)
132 {
133         const gchar *uri;
134
135         uri = webkit_network_request_get_uri (request);
136         empathy_url_show (GTK_WIDGET (view), uri);
137
138         return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
139 }
140
141 static gchar *
142 theme_adium_escape_script (const gchar *text)
143 {
144         const gchar *cur = text;
145         GString     *string;
146
147         string = g_string_sized_new (strlen (text));
148         while (!G_STR_EMPTY (cur)) {
149                 switch (*cur) {
150                 case '\\':
151                         g_string_append (string, "\\\\");       
152                         break;
153                 case '\"':
154                         g_string_append (string, "\\\"");
155                         break;
156                 case '\r':
157                         g_string_append (string, "<br/>");
158                         break;
159                 case '\n':
160                         break;
161                 default:
162                         g_string_append_c (string, *cur);
163                 }
164                 cur++;
165         }
166
167         return g_string_free (string, FALSE);
168 }
169
170 static gchar *
171 theme_adium_parse_body (EmpathyThemeAdium *theme,
172                         const gchar       *text)
173 {
174         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
175         gboolean               use_smileys = FALSE;
176         GSList                *smileys, *l;
177         GString               *string;
178         gint                   i;
179         GRegex                *uri_regex;
180         GMatchInfo            *match_info;
181         gboolean               match;
182         gchar                 *ret = NULL;
183         gchar                 *iter;
184         const gchar           *cur = text;
185
186         empathy_conf_get_bool (empathy_conf_get (),
187                                EMPATHY_PREFS_CHAT_SHOW_SMILEYS,
188                                &use_smileys);
189
190         if (use_smileys) {
191                 /* Replace smileys by a <img/> tag */
192                 string = g_string_sized_new (strlen (cur));
193                 smileys = empathy_smiley_manager_parse (priv->smiley_manager, cur);
194                 for (l = smileys; l; l = l->next) {
195                         EmpathySmiley *smiley;
196
197                         smiley = l->data;
198                         if (smiley->path) {
199                                 g_string_append_printf (string,
200                                                         "<abbr title='%s'><img src=\"%s\"/ alt=\"%s\"/></abbr>",
201                                                         smiley->str, smiley->path, smiley->str);
202                         } else {
203                                 g_string_append (string, smiley->str);
204                         }
205                         empathy_smiley_free (smiley);
206                 }
207                 g_slist_free (smileys);
208
209                 g_free (ret);
210                 cur = ret = g_string_free (string, FALSE);
211         }
212
213         /* Add <a href></a> arround links */
214         uri_regex = empathy_uri_regex_dup_singleton ();
215         match = g_regex_match (uri_regex, cur, 0, &match_info);
216         if (match) {
217                 gint last = 0;
218                 gint s = 0, e = 0;
219
220                 string = g_string_sized_new (strlen (cur));
221                 do {
222                         g_match_info_fetch_pos (match_info, 0, &s, &e);
223
224                         if (s > last) {
225                                 /* Append the text between last link (or the
226                                  * start of the message) and this link */
227                                 g_string_append_len (string, cur + last, s - last);
228                         }
229
230                         /* Append the link inside <a href=""></a> tag */
231                         g_string_append (string, "<a href=\"");
232                         g_string_append_len (string, cur + s, e - s);
233                         g_string_append (string, "\">");
234                         g_string_append_len (string, cur + s, e - s);
235                         g_string_append (string, "</a>");
236
237                         last = e;
238                 } while (g_match_info_next (match_info, NULL));
239
240                 if (e < strlen (cur)) {
241                         /* Append the text after the last link */
242                         g_string_append_len (string, cur + e, strlen (cur) - e);
243                 }
244
245                 g_free (ret);
246                 cur = ret = g_string_free (string, FALSE);
247         }
248         g_match_info_free (match_info);
249         g_regex_unref (uri_regex);
250
251         /* Replace \n by \r so it will be replaced by <br/> */
252         iter = (gchar*) cur;
253         for (i = 0; iter[i] != '\0'; i++) {
254                 if (iter[i] == '\n') {
255                         if (ret == NULL) {
256                                 /* We have changes to make to the string but we
257                                  * are still using the original one.
258                                  * Dup it now */
259                                 cur = iter = ret = g_strdup (cur);
260                         }
261                         iter[i] = '\r';
262                 }               
263         }
264
265         /* If we made changes to the text ret is now a newly allocated string,
266          * otherwise it is NULL to indicate that the original text can be
267          * used without modification */
268         return ret;
269 }
270
271 static void
272 theme_adium_scroll_down (EmpathyChatView *view)
273 {
274         /* Not implemented */
275 }
276
277 #define FOLLOW(cur, str) (!strncmp (cur, str, strlen (str)))
278 static void
279 theme_adium_append_message (EmpathyChatView *view,
280                             EmpathyMessage  *msg)
281 {
282         EmpathyThemeAdium     *theme = EMPATHY_THEME_ADIUM (view);
283         EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
284         EmpathyContact        *sender;
285         gchar                 *dup_body = NULL;
286         const gchar           *body;
287         const gchar           *name;
288         EmpathyAvatar         *avatar;
289         const gchar           *avatar_filename = NULL;
290         time_t                 timestamp;
291         gsize                  len;
292         GString               *string;
293         gchar                 *cur = NULL;
294         gchar                 *prev;
295         gchar                 *script;
296         gchar                 *escape;
297         const gchar           *func;
298
299         if (!priv->page_loaded) {
300                 priv->message_queue = g_list_prepend (priv->message_queue,
301                                                       g_object_ref (msg));
302                 return;
303         }
304
305         /* Get information */
306         sender = empathy_message_get_sender (msg);
307         timestamp = empathy_message_get_timestamp (msg);
308         body = empathy_message_get_body (msg);
309         dup_body = theme_adium_parse_body (theme, body);
310         if (dup_body) {
311                 body = dup_body;
312         }
313         name = empathy_contact_get_name (sender);
314         avatar = empathy_contact_get_avatar (sender);
315         if (avatar) {
316                 avatar_filename = avatar->filename;
317         }
318         if (!avatar_filename) {
319                 if (!priv->default_avatar_filename) {
320                         priv->default_avatar_filename =
321                                 empathy_filename_from_icon_name ("stock_person",
322                                                                  GTK_ICON_SIZE_DIALOG);
323                 }
324                 avatar_filename = priv->default_avatar_filename;
325         }
326
327         /* Get the right html/func to add the message */
328         if (priv->last_contact &&
329             empathy_contact_equal (priv->last_contact, sender)) {
330                 func = "appendNextMessage";
331                 if (empathy_contact_is_user (sender)) {
332                         cur = priv->out_nextcontent_html;
333                         len = priv->out_nextcontent_len;
334                 }
335                 if (!cur) {
336                         cur = priv->in_nextcontent_html;
337                         len = priv->in_nextcontent_len;
338                 }
339         }
340         if (!cur) {
341                 func = "appendMessage";
342                 if (empathy_contact_is_user (sender)) {
343                         cur = priv->out_content_html;
344                         len = priv->out_content_len;
345                 }
346                 if (!cur) {
347                         cur = priv->in_content_html;
348                         len = priv->in_content_len;
349                 }
350         }
351
352         /* Make some search-and-replace in the html code */
353         prev = cur;
354         string = g_string_sized_new (len + strlen (body));
355         while ((cur = strchr (cur, '%'))) {
356                 const gchar *replace = NULL;
357                 gchar       *dup_replace = NULL;
358                 gchar       *fin = NULL;
359
360                 if (FOLLOW (cur, "%message%")) {
361                         replace = body;
362                 } else if (FOLLOW (cur, "%userIconPath%")) {
363                         replace = avatar_filename;
364                 } else if (FOLLOW (cur, "%sender%")) {
365                         replace = name;
366                 } else if (FOLLOW (cur, "%time")) {
367                         gchar *format = NULL;
368                         gchar *start;
369                         gchar *end;
370
371                         /* Extract the time format it provided. */
372                         if (*(start = cur + strlen("%time")) == '{') {
373                                 start++;
374                                 end = strstr (start, "}%");
375                                 if (!end) /* Invalid string */
376                                         continue;
377                                 format = g_strndup (start, end - start);
378                                 fin = end + 1;
379                         } 
380
381                         dup_replace = empathy_time_to_string_local (timestamp,
382                                 format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
383                         replace = dup_replace;
384                         g_free (format);
385                 } else {
386                         cur++;
387                         continue;
388                 }
389
390                 /* Here we have a replacement to make */
391                 g_string_append_len (string, prev, cur - prev);
392                 g_string_append (string, replace);
393                 g_free (dup_replace);
394
395                 /* And update the pointers */
396                 if (fin) {
397                         prev = cur = fin + 1;
398                 } else {
399                         prev = cur = strchr (cur + 1, '%') + 1;
400                 }
401         }
402         g_string_append (string, prev);
403
404         /* Execute a js to add the message */
405         cur = g_string_free (string, FALSE);
406         escape = theme_adium_escape_script (cur);
407         script = g_strdup_printf("%s(\"%s\")", func, escape);
408         webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), script);
409
410         /* Keep the sender of the last displayed message */
411         if (priv->last_contact) {
412                 g_object_unref (priv->last_contact);
413         }
414         priv->last_contact = g_object_ref (sender);
415
416         g_free (dup_body);
417         g_free (cur);
418         g_free (script);
419 }
420 #undef FOLLOW
421
422 static void
423 theme_adium_append_event (EmpathyChatView *view,
424                           const gchar     *str)
425 {
426         /* Not implemented */
427 }
428
429 static void
430 theme_adium_scroll (EmpathyChatView *view,
431                     gboolean         allow_scrolling)
432 {
433         /* Not implemented */
434 }
435
436 static gboolean
437 theme_adium_get_has_selection (EmpathyChatView *view)
438 {
439         /* Not implemented */
440         return FALSE;
441 }
442
443 static void
444 theme_adium_clear (EmpathyChatView *view)
445 {
446         /* Not implemented */
447 }
448
449 static gboolean
450 theme_adium_find_previous (EmpathyChatView *view,
451                            const gchar     *search_criteria,
452                            gboolean         new_search)
453 {
454         /* Not implemented */
455         return FALSE;
456 }
457
458 static gboolean
459 theme_adium_find_next (EmpathyChatView *view,
460                        const gchar     *search_criteria,
461                        gboolean         new_search)
462 {
463         /* Not implemented */
464         return FALSE;
465 }
466
467 static void
468 theme_adium_find_abilities (EmpathyChatView *view,
469                             const gchar    *search_criteria,
470                             gboolean       *can_do_previous,
471                             gboolean       *can_do_next)
472 {
473         /* Not implemented */
474 }
475
476 static void
477 theme_adium_highlight (EmpathyChatView *view,
478                        const gchar     *text)
479 {
480         /* Not implemented */
481 }
482
483 static void
484 theme_adium_copy_clipboard (EmpathyChatView *view)
485 {
486         /* Not implemented */
487 }
488
489 static void
490 theme_adium_iface_init (EmpathyChatViewIface *iface)
491 {
492         iface->append_message = theme_adium_append_message;
493         iface->append_event = theme_adium_append_event;
494         iface->scroll = theme_adium_scroll;
495         iface->scroll_down = theme_adium_scroll_down;
496         iface->get_has_selection = theme_adium_get_has_selection;
497         iface->clear = theme_adium_clear;
498         iface->find_previous = theme_adium_find_previous;
499         iface->find_next = theme_adium_find_next;
500         iface->find_abilities = theme_adium_find_abilities;
501         iface->highlight = theme_adium_highlight;
502         iface->copy_clipboard = theme_adium_copy_clipboard;
503 }
504
505 static void
506 theme_adium_load_finished_cb (WebKitWebView  *view,
507                               WebKitWebFrame *frame,
508                               gpointer        user_data)
509 {
510         EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
511         EmpathyChatView       *chat_view = EMPATHY_CHAT_VIEW (view);
512
513         DEBUG ("Page loaded");
514         priv->page_loaded = TRUE;
515
516         /* Display queued messages */
517         priv->message_queue = g_list_reverse (priv->message_queue);
518         while (priv->message_queue) {
519                 EmpathyMessage *message = priv->message_queue->data;
520
521                 theme_adium_append_message (chat_view, message);
522                 priv->message_queue = g_list_remove (priv->message_queue, message);
523                 g_object_unref (message);
524         }
525 }
526
527 static void
528 theme_adium_finalize (GObject *object)
529 {
530         EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
531
532         g_free (priv->in_content_html);
533         g_free (priv->in_nextcontent_html);
534         g_free (priv->out_content_html);
535         g_free (priv->out_nextcontent_html);
536         g_free (priv->default_avatar_filename);
537         g_object_unref (priv->smiley_manager);
538
539         if (priv->last_contact) {
540                 g_object_unref (priv->last_contact);
541         }
542
543         G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
544 }
545
546 static void
547 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
548 {
549         GObjectClass *object_class = G_OBJECT_CLASS (klass);
550         
551         object_class->finalize = theme_adium_finalize;
552
553         g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
554 }
555
556 static void
557 empathy_theme_adium_init (EmpathyThemeAdium *theme)
558 {
559         EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
560                 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
561
562         theme->priv = priv;     
563
564         priv->smiley_manager = empathy_smiley_manager_new ();
565
566         g_signal_connect (theme, "load-finished",
567                           G_CALLBACK (theme_adium_load_finished_cb),
568                           NULL);
569         g_signal_connect (theme, "navigation-requested",
570                           G_CALLBACK (theme_adium_navigation_requested_cb),
571                           NULL);
572
573
574         theme_adium_load (theme);
575 }
576
577 EmpathyThemeAdium *
578 empathy_theme_adium_new (void)
579 {
580         return g_object_new (EMPATHY_TYPE_THEME_ADIUM, NULL);
581 }
582
583 gboolean
584 empathy_theme_adium_is_valid (const gchar *path)
585 {
586         return TRUE;
587 }
588