Don't try to access empathy_adium_info_* stuff when webkit support is disabled
[empathy-mirror.git] / libempathy-gtk / empathy-theme-adium.c
blobbb2b189ca465703fed653b2e37c51c93a2c41e74
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2008-2009 Collabora Ltd.
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.
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.
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
19 * Authors: Xavier Claessens <xclaesse@gmail.com>
22 #include "config.h"
24 #include <string.h>
25 #include <glib/gi18n-lib.h>
27 #include <webkit/webkit.h>
28 #include <telepathy-glib/dbus.h>
29 #include <telepathy-glib/util.h>
31 #include <pango/pango.h>
32 #include <gdk/gdk.h>
34 #include <libempathy/empathy-gsettings.h>
35 #include <libempathy/empathy-time.h>
36 #include <libempathy/empathy-utils.h>
38 #include "empathy-theme-adium.h"
39 #include "empathy-smiley-manager.h"
40 #include "empathy-ui-utils.h"
41 #include "empathy-plist.h"
42 #include "empathy-string-parser.h"
43 #include "empathy-images.h"
45 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
46 #include <libempathy/empathy-debug.h>
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
50 #define BORING_DPI_DEFAULT 96
52 /* "Join" consecutive messages with timestamps within five minutes */
53 #define MESSAGE_JOIN_PERIOD 5*60
55 typedef struct {
56 EmpathyAdiumData *data;
57 EmpathySmileyManager *smiley_manager;
58 EmpathyContact *last_contact;
59 gint64 last_timestamp;
60 gboolean last_is_backlog;
61 guint pages_loading;
62 /* Queue of QueuedItem*s containing an EmpathyMessage or string */
63 GQueue message_queue;
64 /* Queue of owned gchar* of message token to remove unread
65 * marker for when we lose focus. */
66 GQueue acked_messages;
67 GtkWidget *inspector_window;
68 GSettings *gsettings_chat;
69 gboolean has_focus;
70 gboolean has_unread_message;
71 gboolean allow_scrolling;
72 gchar *variant;
73 gboolean in_construction;
74 } EmpathyThemeAdiumPriv;
76 struct _EmpathyAdiumData {
77 gint ref_count;
78 gchar *path;
79 gchar *basedir;
80 gchar *default_avatar_filename;
81 gchar *default_incoming_avatar_filename;
82 gchar *default_outgoing_avatar_filename;
83 GHashTable *info;
84 guint version;
85 gboolean custom_template;
86 /* gchar* -> gchar* both owned */
87 GHashTable *date_format_cache;
89 /* HTML bits */
90 const gchar *template_html;
91 const gchar *content_html;
92 const gchar *in_content_html;
93 const gchar *in_context_html;
94 const gchar *in_nextcontent_html;
95 const gchar *in_nextcontext_html;
96 const gchar *out_content_html;
97 const gchar *out_context_html;
98 const gchar *out_nextcontent_html;
99 const gchar *out_nextcontext_html;
100 const gchar *status_html;
102 /* Above html strings are pointers to strings stored in this array.
103 * We do this because of fallbacks, some htmls could be pointing the
104 * same string. */
105 GPtrArray *strings_to_free;
108 static void theme_adium_iface_init (EmpathyChatViewIface *iface);
109 static gchar * adium_info_dup_path_for_variant (GHashTable *info, const gchar *variant);
111 enum {
112 PROP_0,
113 PROP_ADIUM_DATA,
114 PROP_VARIANT,
117 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
118 WEBKIT_TYPE_WEB_VIEW,
119 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
120 theme_adium_iface_init));
122 enum {
123 QUEUED_EVENT,
124 QUEUED_MESSAGE,
125 QUEUED_EDIT
128 typedef struct {
129 guint type;
130 EmpathyMessage *msg;
131 char *str;
132 } QueuedItem;
134 static QueuedItem *
135 queue_item (GQueue *queue,
136 guint type,
137 EmpathyMessage *msg,
138 const char *str)
140 QueuedItem *item = g_slice_new0 (QueuedItem);
142 item->type = type;
143 if (msg != NULL)
144 item->msg = g_object_ref (msg);
145 item->str = g_strdup (str);
147 g_queue_push_tail (queue, item);
149 return item;
152 static void
153 free_queued_item (QueuedItem *item)
155 tp_clear_object (&item->msg);
156 g_free (item->str);
158 g_slice_free (QueuedItem, item);
161 static void
162 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
164 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
165 WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
166 gboolean enable_webkit_developer_tools;
168 enable_webkit_developer_tools = g_settings_get_boolean (
169 priv->gsettings_chat,
170 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS);
172 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
173 "enable-developer-extras",
174 enable_webkit_developer_tools,
175 NULL);
178 static void
179 theme_adium_notify_enable_webkit_developer_tools_cb (GSettings *gsettings,
180 const gchar *key,
181 gpointer user_data)
183 EmpathyThemeAdium *theme = user_data;
185 theme_adium_update_enable_webkit_developer_tools (theme);
188 static gboolean
189 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView *view,
190 WebKitWebFrame *web_frame,
191 WebKitNetworkRequest *request,
192 WebKitWebNavigationAction *action,
193 WebKitWebPolicyDecision *decision,
194 gpointer data)
196 const gchar *uri;
198 /* Only call url_show on clicks */
199 if (webkit_web_navigation_action_get_reason (action) !=
200 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
201 webkit_web_policy_decision_use (decision);
202 return TRUE;
205 uri = webkit_network_request_get_uri (request);
206 empathy_url_show (GTK_WIDGET (view), uri);
208 webkit_web_policy_decision_ignore (decision);
209 return TRUE;
212 static void
213 theme_adium_copy_address_cb (GtkMenuItem *menuitem,
214 gpointer user_data)
216 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
217 gchar *uri;
218 GtkClipboard *clipboard;
220 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
222 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
223 gtk_clipboard_set_text (clipboard, uri, -1);
225 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
226 gtk_clipboard_set_text (clipboard, uri, -1);
228 g_free (uri);
231 static void
232 theme_adium_open_address_cb (GtkMenuItem *menuitem,
233 gpointer user_data)
235 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
236 gchar *uri;
238 g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
240 empathy_url_show (GTK_WIDGET (menuitem), uri);
242 g_free (uri);
245 /* Replace each %@ in format with string passed in args */
246 static gchar *
247 string_with_format (const gchar *format,
248 const gchar *first_string,
249 ...)
251 va_list args;
252 const gchar *str;
253 GString *result;
255 va_start (args, first_string);
256 result = g_string_sized_new (strlen (format));
257 for (str = first_string; str != NULL; str = va_arg (args, const gchar *)) {
258 const gchar *next;
260 next = strstr (format, "%@");
261 if (next == NULL) {
262 break;
265 g_string_append_len (result, format, next - format);
266 g_string_append (result, str);
267 format = next + 2;
269 g_string_append (result, format);
270 va_end (args);
272 return g_string_free (result, FALSE);
275 static void
276 theme_adium_load_template (EmpathyThemeAdium *theme)
278 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
279 gchar *basedir_uri;
280 gchar *variant_path;
281 gchar *template;
283 priv->pages_loading++;
284 basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
285 variant_path = adium_info_dup_path_for_variant (priv->data->info,
286 priv->variant);
287 template = string_with_format (priv->data->template_html,
288 variant_path, NULL);
289 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (theme),
290 template, basedir_uri);
291 g_free (basedir_uri);
292 g_free (variant_path);
293 g_free (template);
296 static void
297 theme_adium_match_newline (const gchar *text,
298 gssize len,
299 EmpathyStringReplace replace_func,
300 EmpathyStringParser *sub_parsers,
301 gpointer user_data)
303 GString *string = user_data;
304 gint i;
305 gint prev = 0;
307 if (len < 0) {
308 len = G_MAXSSIZE;
311 /* Replace \n by <br/> */
312 for (i = 0; i < len && text[i] != '\0'; i++) {
313 if (text[i] == '\n') {
314 empathy_string_parser_substr (text + prev,
315 i - prev, sub_parsers,
316 user_data);
317 g_string_append (string, "<br/>");
318 prev = i + 1;
321 empathy_string_parser_substr (text + prev, i - prev,
322 sub_parsers, user_data);
325 static void
326 theme_adium_replace_smiley (const gchar *text,
327 gssize len,
328 gpointer match_data,
329 gpointer user_data)
331 EmpathySmileyHit *hit = match_data;
332 GString *string = user_data;
334 /* Replace smiley by a <img/> tag */
335 g_string_append_printf (string,
336 "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
337 hit->path, (int)len, text, (int)len, text);
340 static EmpathyStringParser string_parsers[] = {
341 {empathy_string_match_link, empathy_string_replace_link},
342 {theme_adium_match_newline, NULL},
343 {empathy_string_match_all, empathy_string_replace_escaped},
344 {NULL, NULL}
347 static EmpathyStringParser string_parsers_with_smiley[] = {
348 {empathy_string_match_link, empathy_string_replace_link},
349 {empathy_string_match_smiley, theme_adium_replace_smiley},
350 {theme_adium_match_newline, NULL},
351 {empathy_string_match_all, empathy_string_replace_escaped},
352 {NULL, NULL}
355 static gchar *
356 theme_adium_parse_body (EmpathyThemeAdium *self,
357 const gchar *text,
358 const gchar *token)
360 EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
361 EmpathyStringParser *parsers;
362 GString *string;
364 /* Check if we have to parse smileys */
365 if (g_settings_get_boolean (priv->gsettings_chat,
366 EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
367 parsers = string_parsers_with_smiley;
368 else
369 parsers = string_parsers;
371 /* Parse text and construct string with links and smileys replaced
372 * by html tags. Also escape text to make sure html code is
373 * displayed verbatim. */
374 string = g_string_sized_new (strlen (text));
376 /* wrap this in HTML that allows us to find the message for later
377 * editing */
378 if (!tp_str_empty (token))
379 g_string_append_printf (string,
380 "<span id=\"message-token-%s\">",
381 token);
383 empathy_string_parser_substr (text, -1, parsers, string);
385 if (!tp_str_empty (token))
386 g_string_append (string, "</span>");
388 /* Wrap body in order to make tabs and multiple spaces displayed
389 * properly. See bug #625745. */
390 g_string_prepend (string, "<div style=\"display: inline; "
391 "white-space: pre-wrap\"'>");
392 g_string_append (string, "</div>");
394 return g_string_free (string, FALSE);
397 static void
398 escape_and_append_len (GString *string, const gchar *str, gint len)
400 while (str != NULL && *str != '\0' && len != 0) {
401 switch (*str) {
402 case '\\':
403 /* \ becomes \\ */
404 g_string_append (string, "\\\\");
405 break;
406 case '\"':
407 /* " becomes \" */
408 g_string_append (string, "\\\"");
409 break;
410 case '\n':
411 /* Remove end of lines */
412 break;
413 default:
414 g_string_append_c (string, *str);
417 str++;
418 len--;
422 /* If *str starts with match, returns TRUE and move pointer to the end */
423 static gboolean
424 theme_adium_match (const gchar **str,
425 const gchar *match)
427 gint len;
429 len = strlen (match);
430 if (strncmp (*str, match, len) == 0) {
431 *str += len - 1;
432 return TRUE;
435 return FALSE;
438 /* Like theme_adium_match() but also return the X part if match is like %foo{X}% */
439 static gboolean
440 theme_adium_match_with_format (const gchar **str,
441 const gchar *match,
442 gchar **format)
444 const gchar *cur = *str;
445 const gchar *end;
447 if (!theme_adium_match (&cur, match)) {
448 return FALSE;
450 cur++;
452 end = strstr (cur, "}%");
453 if (!end) {
454 return FALSE;
457 *format = g_strndup (cur , end - cur);
458 *str = end + 1;
459 return TRUE;
462 /* List of colors used by %senderColor%. Copied from
463 * adium/Frameworks/AIUtilities\ Framework/Source/AIColorAdditions.m
465 static gchar *colors[] = {
466 "aqua", "aquamarine", "blue", "blueviolet", "brown", "burlywood", "cadetblue",
467 "chartreuse", "chocolate", "coral", "cornflowerblue", "crimson", "cyan",
468 "darkblue", "darkcyan", "darkgoldenrod", "darkgreen", "darkgrey", "darkkhaki",
469 "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
470 "darksalmon", "darkseagreen", "darkslateblue", "darkslategrey",
471 "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgrey",
472 "dodgerblue", "firebrick", "forestgreen", "fuchsia", "gold", "goldenrod",
473 "green", "greenyellow", "grey", "hotpink", "indianred", "indigo", "lawngreen",
474 "lightblue", "lightcoral",
475 "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen",
476 "lightskyblue", "lightslategrey", "lightsteelblue", "lime", "limegreen",
477 "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
478 "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen",
479 "mediumturquoise", "mediumvioletred", "midnightblue", "navy", "olive",
480 "olivedrab", "orange", "orangered", "orchid", "palegreen", "paleturquoise",
481 "palevioletred", "peru", "pink", "plum", "powderblue", "purple", "red",
482 "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
483 "sienna", "silver", "skyblue", "slateblue", "slategrey", "springgreen",
484 "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
485 "yellowgreen",
488 static const gchar *
489 nsdate_to_strftime (EmpathyAdiumData *data, const gchar *nsdate)
491 /* Convert from NSDateFormatter (http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/)
492 * to strftime supported by g_date_time_format.
493 * FIXME: table is incomplete, doc of g_date_time_format has a table of
494 * supported tags.
495 * FIXME: g_date_time_format in GLib 2.28 does 0 padding by default, but
496 * in 2.29.x we have to explictely request padding with %0x */
497 static const gchar *convert_table[] = {
498 "a", "%p", // AM/PM
499 "A", NULL, // 0~86399999 (Millisecond of Day)
501 "cccc", "%A", // Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
502 "ccc", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
503 "cc", "%u", // 1~7 (Day of Week)
504 "c", "%u", // 1~7 (Day of Week)
506 "dd", "%d", // 1~31 (0 padded Day of Month)
507 "d", "%d", // 1~31 (0 padded Day of Month)
508 "D", "%j", // 1~366 (0 padded Day of Year)
510 "e", "%u", // 1~7 (0 padded Day of Week)
511 "EEEE", "%A", // Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
512 "EEE", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
513 "EE", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
514 "E", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
516 "F", NULL, // 1~5 (0 padded Week of Month, first day of week = Monday)
518 "g", NULL, // Julian Day Number (number of days since 4713 BC January 1)
519 "GGGG", NULL, // Before Christ/Anno Domini
520 "GGG", NULL, // BC/AD (Era Designator Abbreviated)
521 "GG", NULL, // BC/AD (Era Designator Abbreviated)
522 "G", NULL, // BC/AD (Era Designator Abbreviated)
524 "h", "%I", // 1~12 (0 padded Hour (12hr))
525 "H", "%H", // 0~23 (0 padded Hour (24hr))
527 "k", NULL, // 1~24 (0 padded Hour (24hr)
528 "K", NULL, // 0~11 (0 padded Hour (12hr))
530 "LLLL", "%B", // January/February/March/April/May/June/July/August/September/October/November/December
531 "LLL", "%b", // Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
532 "LL", "%m", // 1~12 (0 padded Month)
533 "L", "%m", // 1~12 (0 padded Month)
535 "m", "%M", // 0~59 (0 padded Minute)
536 "MMMM", "%B", // January/February/March/April/May/June/July/August/September/October/November/December
537 "MMM", "%b", // Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
538 "MM", "%m", // 1~12 (0 padded Month)
539 "M", "%m", // 1~12 (0 padded Month)
541 "qqqq", NULL, // 1st quarter/2nd quarter/3rd quarter/4th quarter
542 "qqq", NULL, // Q1/Q2/Q3/Q4
543 "qq", NULL, // 1~4 (0 padded Quarter)
544 "q", NULL, // 1~4 (0 padded Quarter)
545 "QQQQ", NULL, // 1st quarter/2nd quarter/3rd quarter/4th quarter
546 "QQQ", NULL, // Q1/Q2/Q3/Q4
547 "QQ", NULL, // 1~4 (0 padded Quarter)
548 "Q", NULL, // 1~4 (0 padded Quarter)
550 "s", "%S", // 0~59 (0 padded Second)
551 "S", NULL, // (rounded Sub-Second)
553 "u", "%Y", // (0 padded Year)
555 "vvvv", "%Z", // (General GMT Timezone Name)
556 "vvv", "%Z", // (General GMT Timezone Abbreviation)
557 "vv", "%Z", // (General GMT Timezone Abbreviation)
558 "v", "%Z", // (General GMT Timezone Abbreviation)
560 "w", "%W", // 1~53 (0 padded Week of Year, 1st day of week = Sunday, NB, 1st week of year starts from the last Sunday of last year)
561 "W", NULL, // 1~5 (0 padded Week of Month, 1st day of week = Sunday)
563 "yyyy", "%Y", // (Full Year)
564 "yyy", "%y", // (2 Digits Year)
565 "yy", "%y", // (2 Digits Year)
566 "y", "%Y", // (Full Year)
567 "YYYY", NULL, // (Full Year, starting from the Sunday of the 1st week of year)
568 "YYY", NULL, // (2 Digits Year, starting from the Sunday of the 1st week of year)
569 "YY", NULL, // (2 Digits Year, starting from the Sunday of the 1st week of year)
570 "Y", NULL, // (Full Year, starting from the Sunday of the 1st week of year)
572 "zzzz", NULL, // (Specific GMT Timezone Name)
573 "zzz", NULL, // (Specific GMT Timezone Abbreviation)
574 "zz", NULL, // (Specific GMT Timezone Abbreviation)
575 "z", NULL, // (Specific GMT Timezone Abbreviation)
576 "Z", "%z", // +0000 (RFC 822 Timezone)
578 const gchar *str;
579 GString *string;
580 guint i, j;
582 if (nsdate == NULL) {
583 return NULL;
586 str = g_hash_table_lookup (data->date_format_cache, nsdate);
587 if (str != NULL) {
588 return str;
591 /* Copy nsdate into string, replacing occurences of NSDateFormatter tags
592 * by corresponding strftime tag. */
593 string = g_string_sized_new (strlen (nsdate));
594 for (i = 0; nsdate[i] != '\0'; i++) {
595 gboolean found = FALSE;
597 /* even indexes are NSDateFormatter tag, odd indexes are the
598 * corresponding strftime tag */
599 for (j = 0; j < G_N_ELEMENTS (convert_table); j += 2) {
600 if (g_str_has_prefix (nsdate + i, convert_table[j])) {
601 found = TRUE;
602 break;
605 if (found) {
606 /* If we don't have a replacement, just ignore that tag */
607 if (convert_table[j + 1] != NULL) {
608 g_string_append (string, convert_table[j + 1]);
610 i += strlen (convert_table[j]) - 1;
611 } else {
612 g_string_append_c (string, nsdate[i]);
616 DEBUG ("Date format converted '%s' → '%s'", nsdate, string->str);
618 /* The cache takes ownership of string->str */
619 g_hash_table_insert (data->date_format_cache, g_strdup (nsdate), string->str);
620 return g_string_free (string, FALSE);
624 static void
625 theme_adium_append_html (EmpathyThemeAdium *theme,
626 const gchar *func,
627 const gchar *html,
628 const gchar *message,
629 const gchar *avatar_filename,
630 const gchar *name,
631 const gchar *contact_id,
632 const gchar *service_name,
633 const gchar *message_classes,
634 gint64 timestamp,
635 gboolean is_backlog)
637 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
638 GString *string;
639 const gchar *cur = NULL;
640 gchar *script;
642 /* Make some search-and-replace in the html code */
643 string = g_string_sized_new (strlen (html) + strlen (message));
644 g_string_append_printf (string, "%s(\"", func);
645 for (cur = html; *cur != '\0'; cur++) {
646 const gchar *replace = NULL;
647 gchar *dup_replace = NULL;
648 gchar *format = NULL;
650 /* Those are all well known keywords that needs replacement in
651 * html files. Please keep them in the same order than the adium
652 * spec. See http://trac.adium.im/wiki/CreatingMessageStyles */
653 if (theme_adium_match (&cur, "%userIconPath%")) {
654 replace = avatar_filename;
655 } else if (theme_adium_match (&cur, "%senderScreenName%")) {
656 replace = contact_id;
657 } else if (theme_adium_match (&cur, "%sender%")) {
658 replace = name;
659 } else if (theme_adium_match (&cur, "%senderColor%")) {
660 /* A color derived from the user's name.
661 * FIXME: If a colon separated list of HTML colors is at
662 * Incoming/SenderColors.txt it will be used instead of
663 * the default colors.
665 if (contact_id != NULL) {
666 guint hash = g_str_hash (contact_id);
667 replace = colors[hash % G_N_ELEMENTS (colors)];
669 } else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
670 /* FIXME: The path to the status icon of the sender
671 * (available, away, etc...)
673 } else if (theme_adium_match (&cur, "%messageDirection%")) {
674 /* FIXME: The text direction of the message
675 * (either rtl or ltr)
677 } else if (theme_adium_match (&cur, "%senderDisplayName%")) {
678 /* FIXME: The serverside (remotely set) name of the
679 * sender, such as an MSN display name.
681 * We don't have access to that yet so we use
682 * local alias instead.
684 replace = name;
685 } else if (theme_adium_match_with_format (&cur, "%textbackgroundcolor{", &format)) {
686 /* FIXME: This keyword is used to represent the
687 * highlight background color. "X" is the opacity of the
688 * background, ranges from 0 to 1 and can be any decimal
689 * between.
691 } else if (theme_adium_match (&cur, "%message%")) {
692 replace = message;
693 } else if (theme_adium_match (&cur, "%time%") ||
694 theme_adium_match_with_format (&cur, "%time{", &format)) {
695 const gchar *strftime_format;
697 strftime_format = nsdate_to_strftime (priv->data, format);
698 if (is_backlog) {
699 dup_replace = empathy_time_to_string_local (timestamp,
700 strftime_format ? strftime_format :
701 EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
702 } else {
703 dup_replace = empathy_time_to_string_local (timestamp,
704 strftime_format ? strftime_format :
705 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
707 replace = dup_replace;
708 } else if (theme_adium_match (&cur, "%shortTime%")) {
709 dup_replace = empathy_time_to_string_local (timestamp,
710 EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
711 replace = dup_replace;
712 } else if (theme_adium_match (&cur, "%service%")) {
713 replace = service_name;
714 } else if (theme_adium_match (&cur, "%variant%")) {
715 /* FIXME: The name of the active message style variant,
716 * with all spaces replaced with an underscore.
717 * A variant named "Alternating Messages - Blue Red"
718 * will become "Alternating_Messages_-_Blue_Red".
720 } else if (theme_adium_match (&cur, "%userIcons%")) {
721 /* FIXME: mus t be "hideIcons" if use preference is set
722 * to hide avatars */
723 replace = "showIcons";
724 } else if (theme_adium_match (&cur, "%messageClasses%")) {
725 replace = message_classes;
726 } else if (theme_adium_match (&cur, "%status%")) {
727 /* FIXME: A description of the status event. This is
728 * neither in the user's local language nor expected to
729 * be displayed; it may be useful to use a different div
730 * class to present different types of status messages.
731 * The following is a list of some of the more important
732 * status messages; your message style should be able to
733 * handle being shown a status message not in this list,
734 * as even at present the list is incomplete and is
735 * certain to become out of date in the future:
736 * online
737 * offline
738 * away
739 * away_message
740 * return_away
741 * idle
742 * return_idle
743 * date_separator
744 * contact_joined (group chats)
745 * contact_left
746 * error
747 * timed_out
748 * encryption (all OTR messages use this status)
749 * purple (all IRC topic and join/part messages use this status)
750 * fileTransferStarted
751 * fileTransferCompleted
753 } else {
754 escape_and_append_len (string, cur, 1);
755 continue;
758 /* Here we have a replacement to make */
759 escape_and_append_len (string, replace, -1);
761 g_free (dup_replace);
762 g_free (format);
764 g_string_append (string, "\")");
766 script = g_string_free (string, FALSE);
767 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
768 g_free (script);
771 static void
772 theme_adium_append_event_escaped (EmpathyChatView *view,
773 const gchar *escaped)
775 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
776 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
778 theme_adium_append_html (theme, "appendMessage",
779 priv->data->status_html, escaped, NULL, NULL, NULL,
780 NULL, "event",
781 empathy_time_get_current (), FALSE);
783 /* There is no last contact */
784 if (priv->last_contact) {
785 g_object_unref (priv->last_contact);
786 priv->last_contact = NULL;
790 static void
791 theme_adium_remove_focus_marks (EmpathyThemeAdium *theme,
792 WebKitDOMNodeList *nodes)
794 guint i;
796 /* Remove focus and firstFocus class */
797 for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
798 WebKitDOMNode *node = webkit_dom_node_list_item (nodes, i);
799 WebKitDOMHTMLElement *element = WEBKIT_DOM_HTML_ELEMENT (node);
800 gchar *class_name;
801 gchar **classes, **iter;
802 GString *new_class_name;
803 gboolean first = TRUE;
805 if (element == NULL) {
806 continue;
809 class_name = webkit_dom_html_element_get_class_name (element);
810 classes = g_strsplit (class_name, " ", -1);
811 new_class_name = g_string_sized_new (strlen (class_name));
812 for (iter = classes; *iter != NULL; iter++) {
813 if (tp_strdiff (*iter, "focus") &&
814 tp_strdiff (*iter, "firstFocus")) {
815 if (!first) {
816 g_string_append_c (new_class_name, ' ');
818 g_string_append (new_class_name, *iter);
819 first = FALSE;
823 webkit_dom_html_element_set_class_name (element, new_class_name->str);
825 g_free (class_name);
826 g_strfreev (classes);
827 g_string_free (new_class_name, TRUE);
831 static void
832 theme_adium_remove_all_focus_marks (EmpathyThemeAdium *theme)
834 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
835 WebKitDOMDocument *dom;
836 WebKitDOMNodeList *nodes;
837 GError *error = NULL;
839 if (!priv->has_unread_message)
840 return;
842 priv->has_unread_message = FALSE;
844 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
845 if (dom == NULL) {
846 return;
849 /* Get all nodes with focus class */
850 nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
851 if (nodes == NULL) {
852 DEBUG ("Error getting focus nodes: %s",
853 error ? error->message : "No error");
854 g_clear_error (&error);
855 return;
858 theme_adium_remove_focus_marks (theme, nodes);
861 static void
862 theme_adium_append_message (EmpathyChatView *view,
863 EmpathyMessage *msg)
865 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
866 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
867 EmpathyContact *sender;
868 TpMessage *tp_msg;
869 TpAccount *account;
870 gchar *body_escaped;
871 const gchar *name;
872 const gchar *contact_id;
873 EmpathyAvatar *avatar;
874 const gchar *avatar_filename = NULL;
875 gint64 timestamp;
876 const gchar *html = NULL;
877 const gchar *func;
878 const gchar *service_name;
879 GString *message_classes = NULL;
880 gboolean is_backlog;
881 gboolean consecutive;
882 gboolean action;
884 if (priv->pages_loading != 0) {
885 queue_item (&priv->message_queue, QUEUED_MESSAGE, msg, NULL);
886 return;
889 /* Get information */
890 sender = empathy_message_get_sender (msg);
891 account = empathy_contact_get_account (sender);
892 service_name = empathy_protocol_name_to_display_name
893 (tp_account_get_protocol (account));
894 if (service_name == NULL)
895 service_name = tp_account_get_protocol (account);
896 timestamp = empathy_message_get_timestamp (msg);
897 body_escaped = theme_adium_parse_body (theme,
898 empathy_message_get_body (msg),
899 empathy_message_get_token (msg));
900 name = empathy_contact_get_alias (sender);
901 contact_id = empathy_contact_get_id (sender);
902 action = (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
904 /* If this is a /me probably */
905 if (action) {
906 gchar *str;
908 if (priv->data->version >= 4 || !priv->data->custom_template) {
909 str = g_strdup_printf ("<span class='actionMessageUserName'>%s</span>"
910 "<span class='actionMessageBody'>%s</span>",
911 name, body_escaped);
912 } else {
913 str = g_strdup_printf ("*%s*", body_escaped);
915 g_free (body_escaped);
916 body_escaped = str;
919 /* Get the avatar filename, or a fallback */
920 avatar = empathy_contact_get_avatar (sender);
921 if (avatar) {
922 avatar_filename = avatar->filename;
924 if (!avatar_filename) {
925 if (empathy_contact_is_user (sender)) {
926 avatar_filename = priv->data->default_outgoing_avatar_filename;
927 } else {
928 avatar_filename = priv->data->default_incoming_avatar_filename;
930 if (!avatar_filename) {
931 if (!priv->data->default_avatar_filename) {
932 priv->data->default_avatar_filename =
933 empathy_filename_from_icon_name (EMPATHY_IMAGE_AVATAR_DEFAULT,
934 GTK_ICON_SIZE_DIALOG);
936 avatar_filename = priv->data->default_avatar_filename;
940 /* We want to join this message with the last one if
941 * - senders are the same contact,
942 * - last message was recieved recently,
943 * - last message and this message both are/aren't backlog, and
944 * - DisableCombineConsecutive is not set in theme's settings */
945 is_backlog = empathy_message_is_backlog (msg);
946 consecutive = empathy_contact_equal (priv->last_contact, sender) &&
947 (timestamp - priv->last_timestamp < MESSAGE_JOIN_PERIOD) &&
948 (is_backlog == priv->last_is_backlog) &&
949 !tp_asv_get_boolean (priv->data->info,
950 "DisableCombineConsecutive", NULL);
952 /* Define message classes */
953 message_classes = g_string_new ("message");
954 if (!priv->has_focus && !is_backlog) {
955 if (!priv->has_unread_message) {
956 g_string_append (message_classes, " firstFocus");
957 priv->has_unread_message = TRUE;
959 g_string_append (message_classes, " focus");
961 if (is_backlog) {
962 g_string_append (message_classes, " history");
964 if (consecutive) {
965 g_string_append (message_classes, " consecutive");
967 if (empathy_contact_is_user (sender)) {
968 g_string_append (message_classes, " outgoing");
969 } else {
970 g_string_append (message_classes, " incoming");
972 if (empathy_message_should_highlight (msg)) {
973 g_string_append (message_classes, " mention");
975 if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
976 g_string_append (message_classes, " autoreply");
978 if (action) {
979 g_string_append (message_classes, " action");
981 /* FIXME: other classes:
982 * status - the message is a status change
983 * event - the message is a notification of something happening
984 * (for example, encryption being turned on)
985 * %status% - See %status% in theme_adium_append_html ()
988 /* This is slightly a hack, but it's the only way to add
989 * arbitrary data to messages in the HTML. We add another
990 * class called "x-empathy-message-id-*" to the message. This
991 * way, we can remove the unread marker for this specific
992 * message later. */
993 tp_msg = empathy_message_get_tp_message (msg);
994 if (tp_msg != NULL) {
995 gchar *tmp = tp_escape_as_identifier (
996 tp_message_get_token (tp_msg));
997 g_string_append_printf (message_classes,
998 " x-empathy-message-id-%s", tmp);
999 g_free (tmp);
1002 /* Define javascript function to use */
1003 if (consecutive) {
1004 func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
1005 } else {
1006 func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
1009 if (empathy_contact_is_user (sender)) {
1010 /* out */
1011 if (is_backlog) {
1012 /* context */
1013 html = consecutive ? priv->data->out_nextcontext_html : priv->data->out_context_html;
1014 } else {
1015 /* content */
1016 html = consecutive ? priv->data->out_nextcontent_html : priv->data->out_content_html;
1019 /* remove all the unread marks when we are sending a message */
1020 theme_adium_remove_all_focus_marks (theme);
1021 } else {
1022 /* in */
1023 if (is_backlog) {
1024 /* context */
1025 html = consecutive ? priv->data->in_nextcontext_html : priv->data->in_context_html;
1026 } else {
1027 /* content */
1028 html = consecutive ? priv->data->in_nextcontent_html : priv->data->in_content_html;
1032 theme_adium_append_html (theme, func, html, body_escaped,
1033 avatar_filename, name, contact_id,
1034 service_name, message_classes->str,
1035 timestamp, is_backlog);
1037 /* Keep the sender of the last displayed message */
1038 if (priv->last_contact) {
1039 g_object_unref (priv->last_contact);
1041 priv->last_contact = g_object_ref (sender);
1042 priv->last_timestamp = timestamp;
1043 priv->last_is_backlog = is_backlog;
1045 g_free (body_escaped);
1046 g_string_free (message_classes, TRUE);
1049 static void
1050 theme_adium_append_event (EmpathyChatView *view,
1051 const gchar *str)
1053 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1054 gchar *str_escaped;
1056 if (priv->pages_loading != 0) {
1057 queue_item (&priv->message_queue, QUEUED_EVENT, NULL, str);
1058 return;
1061 str_escaped = g_markup_escape_text (str, -1);
1062 theme_adium_append_event_escaped (view, str_escaped);
1063 g_free (str_escaped);
1066 static void
1067 theme_adium_edit_message (EmpathyChatView *view,
1068 EmpathyMessage *message)
1070 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1071 WebKitDOMDocument *doc;
1072 WebKitDOMElement *span;
1073 gchar *id, *parsed_body;
1074 gchar *tooltip, *timestamp;
1075 GtkIconInfo *icon_info;
1076 GError *error = NULL;
1078 if (priv->pages_loading != 0) {
1079 queue_item (&priv->message_queue, QUEUED_EDIT, message, NULL);
1080 return;
1083 id = g_strdup_printf ("message-token-%s",
1084 empathy_message_get_supersedes (message));
1085 /* we don't pass a token here, because doing so will return another
1086 * <span> element, and we don't want nested <span> elements */
1087 parsed_body = theme_adium_parse_body (EMPATHY_THEME_ADIUM (view),
1088 empathy_message_get_body (message), NULL);
1090 /* find the element */
1091 doc = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
1092 span = webkit_dom_document_get_element_by_id (doc, id);
1094 if (span == NULL) {
1095 DEBUG ("Failed to find id '%s'", id);
1096 goto except;
1099 if (!WEBKIT_DOM_IS_HTML_ELEMENT (span)) {
1100 DEBUG ("Not a HTML element");
1101 goto except;
1104 /* update the HTML */
1105 webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (span),
1106 parsed_body, &error);
1108 if (error != NULL) {
1109 DEBUG ("Error setting new inner-HTML: %s", error->message);
1110 g_error_free (error);
1111 goto except;
1114 /* set a tooltip */
1115 timestamp = empathy_time_to_string_local (
1116 empathy_message_get_timestamp (message),
1117 "%H:%M:%S");
1118 tooltip = g_strdup_printf (_("Message edited at %s"), timestamp);
1120 webkit_dom_html_element_set_title (WEBKIT_DOM_HTML_ELEMENT (span),
1121 tooltip);
1123 g_free (tooltip);
1124 g_free (timestamp);
1126 /* mark this message as edited */
1127 icon_info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
1128 EMPATHY_IMAGE_EDIT_MESSAGE, 16, 0);
1130 if (icon_info != NULL) {
1131 /* set the icon as a background image using CSS
1132 * FIXME: the icon won't update in response to theme changes */
1133 gchar *style = g_strdup_printf (
1134 "background-image:url('%s');"
1135 "background-repeat:no-repeat;"
1136 "background-position:left center;"
1137 "padding-left:19px;", /* 16px icon + 3px padding */
1138 gtk_icon_info_get_filename (icon_info));
1140 webkit_dom_element_set_attribute (span, "style", style, &error);
1142 if (error != NULL) {
1143 DEBUG ("Error setting element style: %s",
1144 error->message);
1145 g_clear_error (&error);
1146 /* not fatal */
1149 g_free (style);
1150 gtk_icon_info_free (icon_info);
1153 goto finally;
1155 except:
1156 DEBUG ("Could not find message to edit with: %s",
1157 empathy_message_get_body (message));
1159 finally:
1160 g_free (id);
1161 g_free (parsed_body);
1164 static void
1165 theme_adium_scroll (EmpathyChatView *view,
1166 gboolean allow_scrolling)
1168 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1170 priv->allow_scrolling = allow_scrolling;
1171 if (allow_scrolling) {
1172 empathy_chat_view_scroll_down (view);
1176 static void
1177 theme_adium_scroll_down (EmpathyChatView *view)
1179 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
1182 static gboolean
1183 theme_adium_get_has_selection (EmpathyChatView *view)
1185 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view));
1188 static void
1189 theme_adium_clear (EmpathyChatView *view)
1191 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1193 theme_adium_load_template (EMPATHY_THEME_ADIUM (view));
1195 /* Clear last contact to avoid trying to add a 'joined'
1196 * message when we don't have an insertion point. */
1197 if (priv->last_contact) {
1198 g_object_unref (priv->last_contact);
1199 priv->last_contact = NULL;
1203 static gboolean
1204 theme_adium_find_previous (EmpathyChatView *view,
1205 const gchar *search_criteria,
1206 gboolean new_search,
1207 gboolean match_case)
1209 /* FIXME: Doesn't respect new_search */
1210 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
1211 search_criteria, match_case,
1212 FALSE, TRUE);
1215 static gboolean
1216 theme_adium_find_next (EmpathyChatView *view,
1217 const gchar *search_criteria,
1218 gboolean new_search,
1219 gboolean match_case)
1221 /* FIXME: Doesn't respect new_search */
1222 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view),
1223 search_criteria, match_case,
1224 TRUE, TRUE);
1227 static void
1228 theme_adium_find_abilities (EmpathyChatView *view,
1229 const gchar *search_criteria,
1230 gboolean match_case,
1231 gboolean *can_do_previous,
1232 gboolean *can_do_next)
1234 /* FIXME: Does webkit provide an API for that? We have wrap=true in
1235 * find_next and find_previous to work around this problem. */
1236 if (can_do_previous)
1237 *can_do_previous = TRUE;
1238 if (can_do_next)
1239 *can_do_next = TRUE;
1242 static void
1243 theme_adium_highlight (EmpathyChatView *view,
1244 const gchar *text,
1245 gboolean match_case)
1247 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view));
1248 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view),
1249 text, match_case, 0);
1250 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view),
1251 TRUE);
1254 static void
1255 theme_adium_copy_clipboard (EmpathyChatView *view)
1257 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
1260 static void
1261 theme_adium_remove_mark_from_message (EmpathyThemeAdium *self,
1262 const gchar *token)
1264 WebKitDOMDocument *dom;
1265 WebKitDOMNodeList *nodes;
1266 gchar *class, *tmp;
1267 GError *error = NULL;
1269 dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (self));
1270 if (dom == NULL) {
1271 return;
1274 tmp = tp_escape_as_identifier (token);
1275 class = g_strdup_printf (".x-empathy-message-id-%s", tmp);
1276 g_free (tmp);
1278 /* Get all nodes with focus class */
1279 nodes = webkit_dom_document_query_selector_all (dom, class, &error);
1280 g_free (class);
1282 if (nodes == NULL) {
1283 DEBUG ("Error getting focus nodes: %s",
1284 error ? error->message : "No error");
1285 g_clear_error (&error);
1286 return;
1289 theme_adium_remove_focus_marks (self, nodes);
1292 static void
1293 theme_adium_remove_acked_message_unread_mark_foreach (gpointer data,
1294 gpointer user_data)
1296 EmpathyThemeAdium *self = user_data;
1297 gchar *token = data;
1299 theme_adium_remove_mark_from_message (self, token);
1300 g_free (token);
1303 static void
1304 theme_adium_focus_toggled (EmpathyChatView *view,
1305 gboolean has_focus)
1307 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1309 priv->has_focus = has_focus;
1310 if (!priv->has_focus) {
1311 /* We've lost focus, so let's make sure all the acked
1312 * messages have lost their unread marker. */
1313 g_queue_foreach (&priv->acked_messages,
1314 theme_adium_remove_acked_message_unread_mark_foreach,
1315 view);
1316 g_queue_clear (&priv->acked_messages);
1318 priv->has_unread_message = FALSE;
1322 static void
1323 theme_adium_message_acknowledged (EmpathyChatView *view,
1324 EmpathyMessage *message)
1326 EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
1327 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1328 TpMessage *tp_msg;
1330 tp_msg = empathy_message_get_tp_message (message);
1332 if (tp_msg == NULL) {
1333 return;
1336 /* We only want to actually remove the unread marker if the
1337 * view doesn't have focus. If we did it all the time we would
1338 * never see the unread markers, ever! So, we'll queue these
1339 * up, and when we lose focus, we'll remove the markers. */
1340 if (priv->has_focus) {
1341 g_queue_push_tail (&priv->acked_messages,
1342 g_strdup (tp_message_get_token (tp_msg)));
1343 return;
1346 theme_adium_remove_mark_from_message (self,
1347 tp_message_get_token (tp_msg));
1350 static void
1351 theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
1353 WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
1355 g_object_unref (hit_test_result);
1358 static void
1359 theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
1361 WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
1362 WebKitHitTestResult *hit_test_result;
1363 WebKitHitTestResultContext context;
1364 GtkWidget *menu;
1365 GtkWidget *item;
1367 hit_test_result = webkit_web_view_get_hit_test_result (view, event);
1368 g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
1370 /* The menu */
1371 menu = empathy_context_menu_new (GTK_WIDGET (view));
1373 /* Select all item */
1374 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
1375 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1377 g_signal_connect_swapped (item, "activate",
1378 G_CALLBACK (webkit_web_view_select_all),
1379 view);
1381 /* Copy menu item */
1382 if (webkit_web_view_can_copy_clipboard (view)) {
1383 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
1384 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1386 g_signal_connect_swapped (item, "activate",
1387 G_CALLBACK (webkit_web_view_copy_clipboard),
1388 view);
1391 /* Clear menu item */
1392 item = gtk_separator_menu_item_new ();
1393 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1395 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
1396 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1398 g_signal_connect_swapped (item, "activate",
1399 G_CALLBACK (empathy_chat_view_clear),
1400 view);
1402 /* We will only add the following menu items if we are
1403 * right-clicking a link */
1404 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1405 /* Separator */
1406 item = gtk_separator_menu_item_new ();
1407 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1409 /* Copy Link Address menu item */
1410 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
1411 g_signal_connect (item, "activate",
1412 G_CALLBACK (theme_adium_copy_address_cb),
1413 hit_test_result);
1414 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1416 /* Open Link menu item */
1417 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1418 g_signal_connect (item, "activate",
1419 G_CALLBACK (theme_adium_open_address_cb),
1420 hit_test_result);
1421 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1424 g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
1425 G_CALLBACK (theme_adium_context_menu_selection_done_cb),
1426 hit_test_result);
1428 /* Display the menu */
1429 gtk_widget_show_all (menu);
1430 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1431 event->button, event->time);
1434 static gboolean
1435 theme_adium_button_press_event (GtkWidget *widget, GdkEventButton *event)
1437 if (event->button == 3) {
1438 gboolean developer_tools_enabled;
1440 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget))),
1441 "enable-developer-extras", &developer_tools_enabled, NULL);
1443 /* We currently have no way to add an inspector menu
1444 * item ourselves, so we disable our customized menu
1445 * if the developer extras are enabled. */
1446 if (!developer_tools_enabled) {
1447 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
1448 return TRUE;
1452 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class)->button_press_event (widget, event);
1455 static void
1456 theme_adium_iface_init (EmpathyChatViewIface *iface)
1458 iface->append_message = theme_adium_append_message;
1459 iface->append_event = theme_adium_append_event;
1460 iface->edit_message = theme_adium_edit_message;
1461 iface->scroll = theme_adium_scroll;
1462 iface->scroll_down = theme_adium_scroll_down;
1463 iface->get_has_selection = theme_adium_get_has_selection;
1464 iface->clear = theme_adium_clear;
1465 iface->find_previous = theme_adium_find_previous;
1466 iface->find_next = theme_adium_find_next;
1467 iface->find_abilities = theme_adium_find_abilities;
1468 iface->highlight = theme_adium_highlight;
1469 iface->copy_clipboard = theme_adium_copy_clipboard;
1470 iface->focus_toggled = theme_adium_focus_toggled;
1471 iface->message_acknowledged = theme_adium_message_acknowledged;
1474 static void
1475 theme_adium_load_finished_cb (WebKitWebView *view,
1476 WebKitWebFrame *frame,
1477 gpointer user_data)
1479 EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
1480 EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
1481 GList *l;
1483 DEBUG ("Page loaded");
1484 priv->pages_loading--;
1486 if (priv->pages_loading != 0)
1487 return;
1489 /* Display queued messages */
1490 for (l = priv->message_queue.head; l != NULL; l = l->next) {
1491 QueuedItem *item = l->data;
1493 switch (item->type)
1495 case QUEUED_MESSAGE:
1496 theme_adium_append_message (chat_view, item->msg);
1497 break;
1499 case QUEUED_EDIT:
1500 theme_adium_edit_message (chat_view, item->msg);
1501 break;
1503 case QUEUED_EVENT:
1504 theme_adium_append_event (chat_view, item->str);
1505 break;
1508 free_queued_item (item);
1511 g_queue_clear (&priv->message_queue);
1514 static void
1515 theme_adium_finalize (GObject *object)
1517 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1519 empathy_adium_data_unref (priv->data);
1520 g_object_unref (priv->gsettings_chat);
1522 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
1525 static void
1526 theme_adium_dispose (GObject *object)
1528 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1530 if (priv->smiley_manager) {
1531 g_object_unref (priv->smiley_manager);
1532 priv->smiley_manager = NULL;
1535 if (priv->last_contact) {
1536 g_object_unref (priv->last_contact);
1537 priv->last_contact = NULL;
1540 if (priv->inspector_window) {
1541 gtk_widget_destroy (priv->inspector_window);
1542 priv->inspector_window = NULL;
1545 if (priv->acked_messages.length > 0) {
1546 g_queue_foreach (&priv->acked_messages, (GFunc) g_free, NULL);
1547 g_queue_clear (&priv->acked_messages);
1550 G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
1553 static gboolean
1554 theme_adium_inspector_show_window_cb (WebKitWebInspector *inspector,
1555 EmpathyThemeAdium *theme)
1557 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1559 if (priv->inspector_window) {
1560 gtk_widget_show_all (priv->inspector_window);
1563 return TRUE;
1566 static gboolean
1567 theme_adium_inspector_close_window_cb (WebKitWebInspector *inspector,
1568 EmpathyThemeAdium *theme)
1570 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1572 if (priv->inspector_window) {
1573 gtk_widget_hide (priv->inspector_window);
1576 return TRUE;
1579 static WebKitWebView *
1580 theme_adium_inspect_web_view_cb (WebKitWebInspector *inspector,
1581 WebKitWebView *web_view,
1582 EmpathyThemeAdium *theme)
1584 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1585 GtkWidget *scrolled_window;
1586 GtkWidget *inspector_web_view;
1588 if (!priv->inspector_window) {
1589 /* Create main window */
1590 priv->inspector_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1591 gtk_window_set_default_size (GTK_WINDOW (priv->inspector_window),
1592 800, 600);
1593 g_signal_connect (priv->inspector_window, "delete-event",
1594 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1596 /* Pack a scrolled window */
1597 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1598 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1599 GTK_POLICY_AUTOMATIC,
1600 GTK_POLICY_AUTOMATIC);
1601 gtk_container_add (GTK_CONTAINER (priv->inspector_window),
1602 scrolled_window);
1603 gtk_widget_show (scrolled_window);
1605 /* Pack a webview in the scrolled window. That webview will be
1606 * used to render the inspector tool. */
1607 inspector_web_view = webkit_web_view_new ();
1608 gtk_container_add (GTK_CONTAINER (scrolled_window),
1609 inspector_web_view);
1610 gtk_widget_show (scrolled_window);
1612 return WEBKIT_WEB_VIEW (inspector_web_view);
1615 return NULL;
1618 static PangoFontDescription *
1619 theme_adium_get_default_font (void)
1621 GSettings *gsettings;
1622 PangoFontDescription *pango_fd;
1623 gchar *font_family;
1625 gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
1627 font_family = g_settings_get_string (gsettings,
1628 EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
1630 if (font_family == NULL)
1631 return NULL;
1633 pango_fd = pango_font_description_from_string (font_family);
1634 g_free (font_family);
1635 g_object_unref (gsettings);
1636 return pango_fd;
1639 static void
1640 theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
1641 const gchar *name,
1642 gint size)
1644 g_object_set (w_settings, "default-font-family", name, NULL);
1645 g_object_set (w_settings, "default-font-size", size, NULL);
1648 static void
1649 theme_adium_set_default_font (WebKitWebSettings *w_settings)
1651 PangoFontDescription *default_font_desc;
1652 GdkScreen *current_screen;
1653 gdouble dpi = 0;
1654 gint pango_font_size = 0;
1656 default_font_desc = theme_adium_get_default_font ();
1657 if (default_font_desc == NULL)
1658 return ;
1659 pango_font_size = pango_font_description_get_size (default_font_desc)
1660 / PANGO_SCALE ;
1661 if (pango_font_description_get_size_is_absolute (default_font_desc)) {
1662 current_screen = gdk_screen_get_default ();
1663 if (current_screen != NULL) {
1664 dpi = gdk_screen_get_resolution (current_screen);
1665 } else {
1666 dpi = BORING_DPI_DEFAULT;
1668 pango_font_size = (gint) (pango_font_size / (dpi / 72));
1670 theme_adium_set_webkit_font (w_settings,
1671 pango_font_description_get_family (default_font_desc),
1672 pango_font_size);
1673 pango_font_description_free (default_font_desc);
1676 static void
1677 theme_adium_constructed (GObject *object)
1679 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1680 const gchar *font_family = NULL;
1681 gint font_size = 0;
1682 WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
1683 WebKitWebSettings *webkit_settings;
1684 WebKitWebInspector *webkit_inspector;
1686 /* Set default settings */
1687 font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
1688 font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
1689 webkit_settings = webkit_web_view_get_settings (webkit_view);
1691 if (font_family && font_size) {
1692 theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
1693 } else {
1694 theme_adium_set_default_font (webkit_settings);
1697 /* Setup webkit inspector */
1698 webkit_inspector = webkit_web_view_get_inspector (webkit_view);
1699 g_signal_connect (webkit_inspector, "inspect-web-view",
1700 G_CALLBACK (theme_adium_inspect_web_view_cb),
1701 object);
1702 g_signal_connect (webkit_inspector, "show-window",
1703 G_CALLBACK (theme_adium_inspector_show_window_cb),
1704 object);
1705 g_signal_connect (webkit_inspector, "close-window",
1706 G_CALLBACK (theme_adium_inspector_close_window_cb),
1707 object);
1709 /* Load template */
1710 theme_adium_load_template (EMPATHY_THEME_ADIUM (object));
1712 priv->in_construction = FALSE;
1715 static void
1716 theme_adium_get_property (GObject *object,
1717 guint param_id,
1718 GValue *value,
1719 GParamSpec *pspec)
1721 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1723 switch (param_id) {
1724 case PROP_ADIUM_DATA:
1725 g_value_set_boxed (value, priv->data);
1726 break;
1727 case PROP_VARIANT:
1728 g_value_set_string (value, priv->variant);
1729 break;
1730 default:
1731 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1732 break;
1736 static void
1737 theme_adium_set_property (GObject *object,
1738 guint param_id,
1739 const GValue *value,
1740 GParamSpec *pspec)
1742 EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (object);
1743 EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
1745 switch (param_id) {
1746 case PROP_ADIUM_DATA:
1747 g_assert (priv->data == NULL);
1748 priv->data = g_value_dup_boxed (value);
1749 break;
1750 case PROP_VARIANT:
1751 empathy_theme_adium_set_variant (theme, g_value_get_string (value));
1752 break;
1753 default:
1754 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1755 break;
1759 static void
1760 empathy_theme_adium_class_init (EmpathyThemeAdiumClass *klass)
1762 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1763 GtkWidgetClass* widget_class = GTK_WIDGET_CLASS (klass);
1765 object_class->finalize = theme_adium_finalize;
1766 object_class->dispose = theme_adium_dispose;
1767 object_class->constructed = theme_adium_constructed;
1768 object_class->get_property = theme_adium_get_property;
1769 object_class->set_property = theme_adium_set_property;
1771 widget_class->button_press_event = theme_adium_button_press_event;
1773 g_object_class_install_property (object_class,
1774 PROP_ADIUM_DATA,
1775 g_param_spec_boxed ("adium-data",
1776 "The theme data",
1777 "Data for the adium theme",
1778 EMPATHY_TYPE_ADIUM_DATA,
1779 G_PARAM_CONSTRUCT_ONLY |
1780 G_PARAM_READWRITE |
1781 G_PARAM_STATIC_STRINGS));
1782 g_object_class_install_property (object_class,
1783 PROP_VARIANT,
1784 g_param_spec_string ("variant",
1785 "The theme variant",
1786 "Variant name for the theme",
1787 NULL,
1788 G_PARAM_CONSTRUCT |
1789 G_PARAM_READWRITE |
1790 G_PARAM_STATIC_STRINGS));
1792 g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
1795 static void
1796 empathy_theme_adium_init (EmpathyThemeAdium *theme)
1798 EmpathyThemeAdiumPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (theme,
1799 EMPATHY_TYPE_THEME_ADIUM, EmpathyThemeAdiumPriv);
1801 theme->priv = priv;
1803 priv->in_construction = TRUE;
1804 g_queue_init (&priv->message_queue);
1805 priv->allow_scrolling = TRUE;
1806 priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
1808 g_signal_connect (theme, "load-finished",
1809 G_CALLBACK (theme_adium_load_finished_cb),
1810 NULL);
1811 g_signal_connect (theme, "navigation-policy-decision-requested",
1812 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb),
1813 NULL);
1815 priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1816 g_signal_connect (priv->gsettings_chat,
1817 "changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
1818 G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
1819 theme);
1821 theme_adium_update_enable_webkit_developer_tools (theme);
1824 EmpathyThemeAdium *
1825 empathy_theme_adium_new (EmpathyAdiumData *data,
1826 const gchar *variant)
1828 g_return_val_if_fail (data != NULL, NULL);
1830 return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
1831 "adium-data", data,
1832 "variant", variant,
1833 NULL);
1836 void
1837 empathy_theme_adium_set_variant (EmpathyThemeAdium *theme,
1838 const gchar *variant)
1840 EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
1841 gchar *variant_path;
1842 gchar *script;
1844 if (!tp_strdiff (priv->variant, variant)) {
1845 return;
1848 g_free (priv->variant);
1849 priv->variant = g_strdup (variant);
1851 if (priv->in_construction) {
1852 return;
1855 DEBUG ("Update view with variant: '%s'", variant);
1856 variant_path = adium_info_dup_path_for_variant (priv->data->info,
1857 priv->variant);
1858 script = g_strdup_printf ("setStylesheet(\"mainStyle\",\"%s\");", variant_path);
1860 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
1862 g_free (variant_path);
1863 g_free (script);
1865 g_object_notify (G_OBJECT (theme), "variant");
1868 gboolean
1869 empathy_adium_path_is_valid (const gchar *path)
1871 gboolean ret;
1872 gchar *file;
1874 /* The theme is not valid if there is no Info.plist */
1875 file = g_build_filename (path, "Contents", "Info.plist",
1876 NULL);
1877 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1878 g_free (file);
1880 if (!ret)
1881 return FALSE;
1883 /* We ship a default Template.html as fallback if there is any problem
1884 * with the one inside the theme. The only other required file is
1885 * Content.html OR Incoming/Content.html*/
1886 file = g_build_filename (path, "Contents", "Resources", "Content.html",
1887 NULL);
1888 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1889 g_free (file);
1891 if (!ret) {
1892 file = g_build_filename (path, "Contents", "Resources", "Incoming",
1893 "Content.html", NULL);
1894 ret = g_file_test (file, G_FILE_TEST_EXISTS);
1895 g_free (file);
1898 return ret;
1901 GHashTable *
1902 empathy_adium_info_new (const gchar *path)
1904 gchar *file;
1905 GValue *value;
1906 GHashTable *info = NULL;
1908 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
1910 file = g_build_filename (path, "Contents", "Info.plist", NULL);
1911 value = empathy_plist_parse_from_file (file);
1912 g_free (file);
1914 if (value == NULL)
1915 return NULL;
1917 info = g_value_dup_boxed (value);
1918 tp_g_value_slice_free (value);
1920 /* Insert the theme's path into the hash table,
1921 * keys have to be dupped */
1922 tp_asv_set_string (info, g_strdup ("path"), path);
1924 return info;
1927 static guint
1928 adium_info_get_version (GHashTable *info)
1930 return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
1933 static const gchar *
1934 adium_info_get_no_variant_name (GHashTable *info)
1936 const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
1937 return name ? name : _("Normal");
1940 static gchar *
1941 adium_info_dup_path_for_variant (GHashTable *info,
1942 const gchar *variant)
1944 guint version = adium_info_get_version (info);
1945 const gchar *no_variant = adium_info_get_no_variant_name (info);
1946 GPtrArray *variants;
1947 guint i;
1949 if (version <= 2 && !tp_strdiff (variant, no_variant)) {
1950 return g_strdup ("main.css");
1953 /* Verify the variant exists, fallback to the first one */
1954 variants = empathy_adium_info_get_available_variants (info);
1955 for (i = 0; i < variants->len; i++) {
1956 if (!tp_strdiff (variant, g_ptr_array_index (variants, i))) {
1957 break;
1960 if (i == variants->len) {
1961 DEBUG ("Variant %s does not exist", variant);
1962 variant = g_ptr_array_index (variants, 0);
1965 return g_strdup_printf ("Variants/%s.css", variant);
1969 const gchar *
1970 empathy_adium_info_get_default_variant (GHashTable *info)
1972 if (adium_info_get_version (info) <= 2) {
1973 return adium_info_get_no_variant_name (info);
1976 return tp_asv_get_string (info, "DefaultVariant");
1979 GPtrArray *
1980 empathy_adium_info_get_available_variants (GHashTable *info)
1982 GPtrArray *variants;
1983 const gchar *path;
1984 gchar *dirpath;
1985 GDir *dir;
1987 variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
1988 if (variants != NULL) {
1989 return variants;
1992 variants = g_ptr_array_new_with_free_func (g_free);
1993 tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
1994 G_TYPE_PTR_ARRAY, variants);
1996 path = tp_asv_get_string (info, "path");
1997 dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
1998 dir = g_dir_open (dirpath, 0, NULL);
1999 if (dir != NULL) {
2000 const gchar *name;
2002 for (name = g_dir_read_name (dir);
2003 name != NULL;
2004 name = g_dir_read_name (dir)) {
2005 gchar *display_name;
2007 if (!g_str_has_suffix (name, ".css")) {
2008 continue;
2011 display_name = g_strdup (name);
2012 strstr (display_name, ".css")[0] = '\0';
2013 g_ptr_array_add (variants, display_name);
2015 g_dir_close (dir);
2017 g_free (dirpath);
2019 if (adium_info_get_version (info) <= 2) {
2020 g_ptr_array_add (variants,
2021 g_strdup (adium_info_get_no_variant_name (info)));
2024 return variants;
2027 GType
2028 empathy_adium_data_get_type (void)
2030 static GType type_id = 0;
2032 if (!type_id)
2034 type_id = g_boxed_type_register_static ("EmpathyAdiumData",
2035 (GBoxedCopyFunc) empathy_adium_data_ref,
2036 (GBoxedFreeFunc) empathy_adium_data_unref);
2039 return type_id;
2042 EmpathyAdiumData *
2043 empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
2045 EmpathyAdiumData *data;
2046 gchar *template_html = NULL;
2047 gchar *footer_html = NULL;
2048 gchar *tmp;
2050 g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
2052 data = g_slice_new0 (EmpathyAdiumData);
2053 data->ref_count = 1;
2054 data->path = g_strdup (path);
2055 data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
2056 G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
2057 data->info = g_hash_table_ref (info);
2058 data->version = adium_info_get_version (info);
2059 data->strings_to_free = g_ptr_array_new_with_free_func (g_free);
2060 data->date_format_cache = g_hash_table_new_full (g_str_hash,
2061 g_str_equal, g_free, g_free);
2063 DEBUG ("Loading theme at %s", path);
2065 #define LOAD(path, var) \
2066 tmp = g_build_filename (data->basedir, path, NULL); \
2067 g_file_get_contents (tmp, &var, NULL, NULL); \
2068 g_free (tmp); \
2070 #define LOAD_CONST(path, var) \
2072 gchar *content; \
2073 LOAD (path, content); \
2074 if (content != NULL) { \
2075 g_ptr_array_add (data->strings_to_free, content); \
2077 var = content; \
2080 /* Load html files */
2081 LOAD_CONST ("Content.html", data->content_html);
2082 LOAD_CONST ("Incoming/Content.html", data->in_content_html);
2083 LOAD_CONST ("Incoming/NextContent.html", data->in_nextcontent_html);
2084 LOAD_CONST ("Incoming/Context.html", data->in_context_html);
2085 LOAD_CONST ("Incoming/NextContext.html", data->in_nextcontext_html);
2086 LOAD_CONST ("Outgoing/Content.html", data->out_content_html);
2087 LOAD_CONST ("Outgoing/NextContent.html", data->out_nextcontent_html);
2088 LOAD_CONST ("Outgoing/Context.html", data->out_context_html);
2089 LOAD_CONST ("Outgoing/NextContext.html", data->out_nextcontext_html);
2090 LOAD_CONST ("Status.html", data->status_html);
2091 LOAD ("Template.html", template_html);
2092 LOAD ("Footer.html", footer_html);
2094 #undef LOAD_CONST
2095 #undef LOAD
2097 /* HTML fallbacks: If we have at least content OR in_content, then
2098 * everything else gets a fallback */
2100 #define FALLBACK(html, fallback) \
2101 if (html == NULL) { \
2102 html = fallback; \
2105 /* in_nextcontent -> in_content -> content */
2106 FALLBACK (data->in_content_html, data->content_html);
2107 FALLBACK (data->in_nextcontent_html, data->in_content_html);
2109 /* context -> content */
2110 FALLBACK (data->in_context_html, data->in_content_html);
2111 FALLBACK (data->in_nextcontext_html, data->in_nextcontent_html);
2112 FALLBACK (data->out_context_html, data->out_content_html);
2113 FALLBACK (data->out_nextcontext_html, data->out_nextcontent_html);
2115 /* out -> in */
2116 FALLBACK (data->out_content_html, data->in_content_html);
2117 FALLBACK (data->out_nextcontent_html, data->in_nextcontent_html);
2118 FALLBACK (data->out_context_html, data->in_context_html);
2119 FALLBACK (data->out_nextcontext_html, data->in_nextcontext_html);
2121 /* status -> in_content */
2122 FALLBACK (data->status_html, data->in_content_html);
2124 #undef FALLBACK
2126 /* template -> empathy's template */
2127 data->custom_template = (template_html != NULL);
2128 if (template_html == NULL) {
2129 tmp = empathy_file_lookup ("Template.html", "data");
2130 g_file_get_contents (tmp, &template_html, NULL, NULL);
2131 g_free (tmp);
2134 /* Default avatar */
2135 tmp = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
2136 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
2137 data->default_incoming_avatar_filename = tmp;
2138 } else {
2139 g_free (tmp);
2141 tmp = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
2142 if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
2143 data->default_outgoing_avatar_filename = tmp;
2144 } else {
2145 g_free (tmp);
2148 /* Old custom templates had only 4 parameters.
2149 * New templates have 5 parameters */
2150 if (data->version <= 2 && data->custom_template) {
2151 tmp = string_with_format (template_html,
2152 data->basedir,
2153 "%@", /* Leave variant unset */
2154 "", /* The header */
2155 footer_html ? footer_html : "",
2156 NULL);
2157 } else {
2158 tmp = string_with_format (template_html,
2159 data->basedir,
2160 data->version <= 2 ? "" : "@import url( \"main.css\" );",
2161 "%@", /* Leave variant unset */
2162 "", /* The header */
2163 footer_html ? footer_html : "",
2164 NULL);
2166 g_ptr_array_add (data->strings_to_free, tmp);
2167 data->template_html = tmp;
2169 g_free (template_html);
2170 g_free (footer_html);
2172 return data;
2175 EmpathyAdiumData *
2176 empathy_adium_data_new (const gchar *path)
2178 EmpathyAdiumData *data;
2179 GHashTable *info;
2181 info = empathy_adium_info_new (path);
2182 data = empathy_adium_data_new_with_info (path, info);
2183 g_hash_table_unref (info);
2185 return data;
2188 EmpathyAdiumData *
2189 empathy_adium_data_ref (EmpathyAdiumData *data)
2191 g_return_val_if_fail (data != NULL, NULL);
2193 g_atomic_int_inc (&data->ref_count);
2195 return data;
2198 void
2199 empathy_adium_data_unref (EmpathyAdiumData *data)
2201 g_return_if_fail (data != NULL);
2203 if (g_atomic_int_dec_and_test (&data->ref_count)) {
2204 g_free (data->path);
2205 g_free (data->basedir);
2206 g_free (data->default_avatar_filename);
2207 g_free (data->default_incoming_avatar_filename);
2208 g_free (data->default_outgoing_avatar_filename);
2209 g_hash_table_unref (data->info);
2210 g_ptr_array_unref (data->strings_to_free);
2211 tp_clear_pointer (&data->date_format_cache, g_hash_table_unref);
2213 g_slice_free (EmpathyAdiumData, data);
2217 GHashTable *
2218 empathy_adium_data_get_info (EmpathyAdiumData *data)
2220 g_return_val_if_fail (data != NULL, NULL);
2222 return data->info;
2225 const gchar *
2226 empathy_adium_data_get_path (EmpathyAdiumData *data)
2228 g_return_val_if_fail (data != NULL, NULL);
2230 return data->path;