1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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>
25 #include <glib/gi18n.h>
27 #include <webkit/webkit.h>
28 #include <telepathy-glib/dbus.h>
29 #include <telepathy-glib/util.h>
31 #include <gconf/gconf-client.h>
32 #include <pango/pango.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-conf.h"
41 #include "empathy-ui-utils.h"
42 #include "empathy-plist.h"
44 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
45 #include <libempathy/empathy-debug.h>
47 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyThemeAdium)
49 /* GConf key containing current value of font */
50 #define EMPATHY_GCONF_FONT_KEY_NAME "/desktop/gnome/interface/font_name"
51 #define BORING_DPI_DEFAULT 96
53 /* "Join" consecutive messages with timestamps within five minutes */
54 #define MESSAGE_JOIN_PERIOD 5*60
57 EmpathyAdiumData
*data
;
58 EmpathySmileyManager
*smiley_manager
;
59 EmpathyContact
*last_contact
;
60 time_t last_timestamp
;
61 gboolean last_is_backlog
;
64 guint notify_enable_webkit_developer_tools_id
;
65 GtkWidget
*inspector_window
;
66 } EmpathyThemeAdiumPriv
;
68 struct _EmpathyAdiumData
{
72 gchar
*default_avatar_filename
;
73 gchar
*default_incoming_avatar_filename
;
74 gchar
*default_outgoing_avatar_filename
;
76 gchar
*in_content_html
;
78 gchar
*in_context_html
;
80 gchar
*in_nextcontent_html
;
81 gsize in_nextcontent_len
;
82 gchar
*in_nextcontext_html
;
83 gsize in_nextcontext_len
;
84 gchar
*out_content_html
;
85 gsize out_content_len
;
86 gchar
*out_context_html
;
87 gsize out_context_len
;
88 gchar
*out_nextcontent_html
;
89 gsize out_nextcontent_len
;
90 gchar
*out_nextcontext_html
;
91 gsize out_nextcontext_len
;
97 static void theme_adium_iface_init (EmpathyChatViewIface
*iface
);
104 G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium
, empathy_theme_adium
,
105 WEBKIT_TYPE_WEB_VIEW
,
106 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW
,
107 theme_adium_iface_init
));
110 theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium
*theme
)
112 WebKitWebView
*web_view
= WEBKIT_WEB_VIEW (theme
);
113 gboolean enable_webkit_developer_tools
;
115 if (!empathy_conf_get_bool (empathy_conf_get (),
116 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS
,
117 &enable_webkit_developer_tools
)) {
121 g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view
)),
122 "enable-developer-extras",
123 enable_webkit_developer_tools
,
128 theme_adium_notify_enable_webkit_developer_tools_cb (EmpathyConf
*conf
,
132 EmpathyThemeAdium
*theme
= user_data
;
134 theme_adium_update_enable_webkit_developer_tools (theme
);
138 theme_adium_navigation_policy_decision_requested_cb (WebKitWebView
*view
,
139 WebKitWebFrame
*web_frame
,
140 WebKitNetworkRequest
*request
,
141 WebKitWebNavigationAction
*action
,
142 WebKitWebPolicyDecision
*decision
,
147 /* Only call url_show on clicks */
148 if (webkit_web_navigation_action_get_reason (action
) !=
149 WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
150 webkit_web_policy_decision_use (decision
);
154 uri
= webkit_network_request_get_uri (request
);
155 empathy_url_show (GTK_WIDGET (view
), uri
);
157 webkit_web_policy_decision_ignore (decision
);
162 theme_adium_copy_address_cb (GtkMenuItem
*menuitem
,
165 WebKitHitTestResult
*hit_test_result
= WEBKIT_HIT_TEST_RESULT (user_data
);
167 GtkClipboard
*clipboard
;
169 g_object_get (G_OBJECT (hit_test_result
), "link-uri", &uri
, NULL
);
171 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
172 gtk_clipboard_set_text (clipboard
, uri
, -1);
174 clipboard
= gtk_clipboard_get (GDK_SELECTION_PRIMARY
);
175 gtk_clipboard_set_text (clipboard
, uri
, -1);
181 theme_adium_open_address_cb (GtkMenuItem
*menuitem
,
184 WebKitHitTestResult
*hit_test_result
= WEBKIT_HIT_TEST_RESULT (user_data
);
187 g_object_get (G_OBJECT (hit_test_result
), "link-uri", &uri
, NULL
);
189 empathy_url_show (GTK_WIDGET (menuitem
), uri
);
195 theme_adium_parse_body (EmpathyThemeAdium
*theme
,
198 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
199 gboolean use_smileys
= FALSE
;
204 GMatchInfo
*match_info
;
209 empathy_conf_get_bool (empathy_conf_get (),
210 EMPATHY_PREFS_CHAT_SHOW_SMILEYS
,
213 /* Add <a href></a> arround links */
214 uri_regex
= empathy_uri_regex_dup_singleton ();
215 match
= g_regex_match (uri_regex
, text
, 0, &match_info
);
220 string
= g_string_sized_new (strlen (text
));
224 g_match_info_fetch_pos (match_info
, 0, &s
, &e
);
227 /* Append the text between last link (or the
228 * start of the message) and this link */
230 str
= g_markup_escape_text (text
+ last
, s
- last
);
231 g_string_append (string
, str
);
235 /* Append the link inside <a href=""></a> tag */
236 real_url
= empathy_make_absolute_url_len (text
+ s
, e
- s
);
238 g_string_append (string
, "<a href=\"");
239 g_string_append (string
, real_url
);
240 g_string_append (string
, "\">");
241 g_string_append_len (string
, text
+ s
, e
- s
);
242 g_string_append (string
, "</a>");
246 } while (g_match_info_next (match_info
, NULL
));
248 if (e
< (gint
) strlen (text
)) {
249 /* Append the text after the last link */
251 str
= g_markup_escape_text (text
+ e
, strlen (text
) - e
);
252 g_string_append (string
, str
);
257 text
= ret
= g_string_free (string
, FALSE
);
258 } else if (use_smileys
) {
259 /* Replace smileys by a <img/> tag */
260 string
= g_string_sized_new (strlen (text
));
261 smileys
= empathy_smiley_manager_parse (priv
->smiley_manager
, text
);
262 for (l
= smileys
; l
; l
= l
->next
) {
263 EmpathySmiley
*smiley
;
267 g_string_append_printf (string
,
268 "<abbr title='%s'><img src=\"%s\"/ alt=\"%s\"/></abbr>",
269 smiley
->str
, smiley
->path
, smiley
->str
);
273 str
= g_markup_escape_text (smiley
->str
, -1);
274 g_string_append (string
, str
);
277 empathy_smiley_free (smiley
);
279 g_slist_free (smileys
);
282 text
= ret
= g_string_free (string
, FALSE
);
284 text
= ret
= g_markup_escape_text (text
, -1);
287 g_match_info_free (match_info
);
288 g_regex_unref (uri_regex
);
290 /* Replace \n by <br/> */
293 for (i
= 0; text
[i
] != '\0'; i
++) {
294 if (text
[i
] == '\n') {
296 string
= g_string_sized_new (strlen (text
));
298 g_string_append_len (string
, text
+ prev
, i
- prev
);
299 g_string_append (string
, "<br/>");
304 g_string_append (string
, text
+ prev
);
306 text
= ret
= g_string_free (string
, FALSE
);
313 escape_and_append_len (GString
*string
, const gchar
*str
, gint len
)
315 while (*str
!= '\0' && len
!= 0) {
319 g_string_append (string
, "\\\\");
323 g_string_append (string
, "\\\"");
326 /* Remove end of lines */
329 g_string_append_c (string
, *str
);
338 theme_adium_match (const gchar
**str
, const gchar
*match
)
342 len
= strlen (match
);
343 if (strncmp (*str
, match
, len
) == 0) {
352 theme_adium_append_html (EmpathyThemeAdium
*theme
,
354 const gchar
*html
, gsize len
,
355 const gchar
*message
,
356 const gchar
*avatar_filename
,
358 const gchar
*contact_id
,
359 const gchar
*service_name
,
360 const gchar
*message_classes
,
364 const gchar
*cur
= NULL
;
367 /* Make some search-and-replace in the html code */
368 string
= g_string_sized_new (len
+ strlen (message
));
369 g_string_append_printf (string
, "%s(\"", func
);
370 for (cur
= html
; *cur
!= '\0'; cur
++) {
371 const gchar
*replace
= NULL
;
372 gchar
*dup_replace
= NULL
;
374 if (theme_adium_match (&cur
, "%message%")) {
376 } else if (theme_adium_match (&cur
, "%messageClasses%")) {
377 replace
= message_classes
;
378 } else if (theme_adium_match (&cur
, "%userIconPath%")) {
379 replace
= avatar_filename
;
380 } else if (theme_adium_match (&cur
, "%sender%")) {
382 } else if (theme_adium_match (&cur
, "%senderScreenName%")) {
383 replace
= contact_id
;
384 } else if (theme_adium_match (&cur
, "%senderDisplayName%")) {
385 /* %senderDisplayName% -
386 * "The serverside (remotely set) name of the sender,
387 * such as an MSN display name."
389 * We don't have access to that yet so we use local
392 } else if (theme_adium_match (&cur
, "%service%")) {
393 replace
= service_name
;
394 } else if (theme_adium_match (&cur
, "%shortTime%")) {
395 dup_replace
= empathy_time_to_string_local (timestamp
,
396 EMPATHY_TIME_FORMAT_DISPLAY_SHORT
);
397 replace
= dup_replace
;
398 } else if (theme_adium_match (&cur
, "%time")) {
399 gchar
*format
= NULL
;
401 /* Time can be in 2 formats:
402 * %time% or %time{strftime format}%
403 * Extract the time format if provided. */
406 end
= strstr (cur
, "}%");
411 format
= g_strndup (cur
, end
- cur
);
417 dup_replace
= empathy_time_to_string_local (timestamp
,
418 format
? format
: EMPATHY_TIME_FORMAT_DISPLAY_SHORT
);
419 replace
= dup_replace
;
422 escape_and_append_len (string
, cur
, 1);
426 /* Here we have a replacement to make */
427 escape_and_append_len (string
, replace
, -1);
428 g_free (dup_replace
);
430 g_string_append (string
, "\")");
432 script
= g_string_free (string
, FALSE
);
433 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme
), script
);
438 theme_adium_append_message (EmpathyChatView
*view
,
441 EmpathyThemeAdium
*theme
= EMPATHY_THEME_ADIUM (view
);
442 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
443 EmpathyContact
*sender
;
445 gchar
*dup_body
= NULL
;
448 const gchar
*contact_id
;
449 EmpathyAvatar
*avatar
;
450 const gchar
*avatar_filename
= NULL
;
455 const gchar
*service_name
;
456 GString
*message_classes
= NULL
;
458 gboolean consecutive
;
460 if (!priv
->page_loaded
) {
461 priv
->message_queue
= g_list_prepend (priv
->message_queue
,
466 /* Get information */
467 sender
= empathy_message_get_sender (msg
);
468 account
= empathy_contact_get_account (sender
);
469 service_name
= empathy_protocol_name_to_display_name
470 (tp_account_get_protocol (account
));
471 if (service_name
== NULL
)
472 service_name
= tp_account_get_protocol (account
);
473 timestamp
= empathy_message_get_timestamp (msg
);
474 body
= empathy_message_get_body (msg
);
475 dup_body
= theme_adium_parse_body (theme
, body
);
479 name
= empathy_contact_get_name (sender
);
480 contact_id
= empathy_contact_get_id (sender
);
482 /* If this is a /me, append an event */
483 if (empathy_message_get_tptype (msg
) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION
) {
486 str
= g_strdup_printf ("%s %s", name
, body
);
487 empathy_chat_view_append_event (view
, str
);
493 /* Get the avatar filename, or a fallback */
494 avatar
= empathy_contact_get_avatar (sender
);
496 avatar_filename
= avatar
->filename
;
498 if (!avatar_filename
) {
499 if (empathy_contact_is_user (sender
)) {
500 avatar_filename
= priv
->data
->default_outgoing_avatar_filename
;
502 avatar_filename
= priv
->data
->default_incoming_avatar_filename
;
504 if (!avatar_filename
) {
505 if (!priv
->data
->default_avatar_filename
) {
506 priv
->data
->default_avatar_filename
=
507 empathy_filename_from_icon_name ("stock_person",
508 GTK_ICON_SIZE_DIALOG
);
510 avatar_filename
= priv
->data
->default_avatar_filename
;
514 /* We want to join this message with the last one if
515 * - senders are the same contact,
516 * - last message was recieved recently,
517 * - last message and this message both are/aren't backlog, and
518 * - DisableCombineConsecutive is not set in theme's settings */
519 is_backlog
= empathy_message_is_backlog (msg
);
520 consecutive
= empathy_contact_equal (priv
->last_contact
, sender
) &&
521 (timestamp
- priv
->last_timestamp
< MESSAGE_JOIN_PERIOD
) &&
522 (is_backlog
== priv
->last_is_backlog
) &&
523 !tp_asv_get_boolean (priv
->data
->info
,
524 "DisableCombineConsecutive", NULL
);
526 /* Define message classes */
527 message_classes
= g_string_new ("message");
529 g_string_append (message_classes
, " history");
532 g_string_append (message_classes
, " consecutive");
534 if (empathy_contact_is_user (sender
)) {
535 g_string_append (message_classes
, " outgoing");
537 g_string_append (message_classes
, " incoming");
540 /* Define javascript function to use */
542 func
= "appendNextMessage";
544 func
= "appendMessage";
548 if (empathy_contact_is_user (sender
)) {
551 html
= priv
->data
->out_nextcontext_html
;
552 len
= priv
->data
->out_nextcontext_len
;
555 /* Not backlog, or fallback if NextContext.html
558 html
= priv
->data
->out_nextcontent_html
;
559 len
= priv
->data
->out_nextcontent_len
;
563 /* Not consecutive, or fallback if NextContext.html and/or
564 * NextContent.html are missing */
567 html
= priv
->data
->out_context_html
;
568 len
= priv
->data
->out_context_len
;
572 html
= priv
->data
->out_content_html
;
573 len
= priv
->data
->out_content_len
;
578 /* Incoming, or fallback if outgoing files are missing */
582 html
= priv
->data
->in_nextcontext_html
;
583 len
= priv
->data
->in_nextcontext_len
;
586 /* Note backlog, or fallback if NextContext.html
589 html
= priv
->data
->in_nextcontent_html
;
590 len
= priv
->data
->in_nextcontent_len
;
594 /* Not consecutive, or fallback if NextContext.html and/or
595 * NextContent.html are missing */
598 html
= priv
->data
->in_context_html
;
599 len
= priv
->data
->in_context_len
;
603 html
= priv
->data
->in_content_html
;
604 len
= priv
->data
->in_content_len
;
610 theme_adium_append_html (theme
, func
, html
, len
, body
,
611 avatar_filename
, name
, contact_id
,
612 service_name
, message_classes
->str
,
615 DEBUG ("Couldn't find HTML file for this message");
618 /* Keep the sender of the last displayed message */
619 if (priv
->last_contact
) {
620 g_object_unref (priv
->last_contact
);
622 priv
->last_contact
= g_object_ref (sender
);
623 priv
->last_timestamp
= timestamp
;
624 priv
->last_is_backlog
= is_backlog
;
627 g_string_free (message_classes
, TRUE
);
631 theme_adium_append_event (EmpathyChatView
*view
,
634 EmpathyThemeAdium
*theme
= EMPATHY_THEME_ADIUM (view
);
635 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
637 if (priv
->data
->status_html
) {
640 str_escaped
= g_markup_escape_text (str
, -1);
641 theme_adium_append_html (theme
, "appendMessage",
642 priv
->data
->status_html
,
643 priv
->data
->status_len
,
644 str_escaped
, NULL
, NULL
, NULL
, NULL
,
645 "event", empathy_time_get_current ());
646 g_free (str_escaped
);
649 /* There is no last contact */
650 if (priv
->last_contact
) {
651 g_object_unref (priv
->last_contact
);
652 priv
->last_contact
= NULL
;
657 theme_adium_scroll (EmpathyChatView
*view
,
658 gboolean allow_scrolling
)
660 /* FIXME: Is it possible? I guess we need a js function, but I don't
665 theme_adium_scroll_down (EmpathyChatView
*view
)
667 webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view
), "scrollToBottom()");
671 theme_adium_get_has_selection (EmpathyChatView
*view
)
673 return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (view
));
677 theme_adium_clear (EmpathyChatView
*view
)
679 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (view
);
682 priv
->page_loaded
= FALSE
;
683 basedir_uri
= g_strconcat ("file://", priv
->data
->basedir
, NULL
);
684 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view
),
685 priv
->data
->template_html
,
687 g_free (basedir_uri
);
689 /* Clear last contact to avoid trying to add a 'joined'
690 * message when we don't have an insertion point. */
691 if (priv
->last_contact
) {
692 g_object_unref (priv
->last_contact
);
693 priv
->last_contact
= NULL
;
698 theme_adium_find_previous (EmpathyChatView
*view
,
699 const gchar
*search_criteria
,
702 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view
),
703 search_criteria
, FALSE
,
708 theme_adium_find_next (EmpathyChatView
*view
,
709 const gchar
*search_criteria
,
712 return webkit_web_view_search_text (WEBKIT_WEB_VIEW (view
),
713 search_criteria
, FALSE
,
718 theme_adium_find_abilities (EmpathyChatView
*view
,
719 const gchar
*search_criteria
,
720 gboolean
*can_do_previous
,
721 gboolean
*can_do_next
)
723 /* FIXME: Does webkit provide an API for that? We have wrap=true in
724 * find_next and find_previous to work around this problem. */
726 *can_do_previous
= TRUE
;
732 theme_adium_highlight (EmpathyChatView
*view
,
735 webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (view
));
736 webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (view
),
738 webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (view
),
743 theme_adium_copy_clipboard (EmpathyChatView
*view
)
745 webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view
));
749 theme_adium_context_menu_selection_done_cb (GtkMenuShell
*menu
, gpointer user_data
)
751 WebKitHitTestResult
*hit_test_result
= WEBKIT_HIT_TEST_RESULT (user_data
);
753 g_object_unref (hit_test_result
);
757 theme_adium_context_menu_for_event (EmpathyThemeAdium
*theme
, GdkEventButton
*event
)
759 WebKitWebView
*view
= WEBKIT_WEB_VIEW (theme
);
760 WebKitHitTestResult
*hit_test_result
;
761 WebKitHitTestResultContext context
;
765 hit_test_result
= webkit_web_view_get_hit_test_result (view
, event
);
766 g_object_get (G_OBJECT (hit_test_result
), "context", &context
, NULL
);
769 menu
= gtk_menu_new ();
771 /* Select all item */
772 item
= gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL
, NULL
);
773 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
775 g_signal_connect_swapped (item
, "activate",
776 G_CALLBACK (webkit_web_view_select_all
),
780 if (webkit_web_view_can_copy_clipboard (view
)) {
781 item
= gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY
, NULL
);
782 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
784 g_signal_connect_swapped (item
, "activate",
785 G_CALLBACK (webkit_web_view_copy_clipboard
),
789 /* Clear menu item */
790 item
= gtk_separator_menu_item_new ();
791 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
793 item
= gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR
, NULL
);
794 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
796 g_signal_connect_swapped (item
, "activate",
797 G_CALLBACK (empathy_chat_view_clear
),
800 /* We will only add the following menu items if we are
801 * right-clicking a link */
802 if (context
& WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
) {
804 item
= gtk_separator_menu_item_new ();
805 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
807 /* Copy Link Address menu item */
808 item
= gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
809 g_signal_connect (item
, "activate",
810 G_CALLBACK (theme_adium_copy_address_cb
),
812 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
814 /* Open Link menu item */
815 item
= gtk_menu_item_new_with_mnemonic (_("_Open Link"));
816 g_signal_connect (item
, "activate",
817 G_CALLBACK (theme_adium_open_address_cb
),
819 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
822 g_signal_connect (GTK_MENU_SHELL (menu
), "selection-done",
823 G_CALLBACK (theme_adium_context_menu_selection_done_cb
),
826 /* Display the menu */
827 gtk_widget_show_all (menu
);
828 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
,
829 event
->button
, event
->time
);
833 theme_adium_button_press_event (GtkWidget
*widget
, GdkEventButton
*event
)
835 if (event
->button
== 3) {
836 gboolean developer_tools_enabled
;
838 g_object_get (G_OBJECT (webkit_web_view_get_settings (WEBKIT_WEB_VIEW (widget
))),
839 "enable-developer-extras", &developer_tools_enabled
, NULL
);
841 /* We currently have no way to add an inspector menu
842 * item ourselves, so we disable our customized menu
843 * if the developer extras are enabled. */
844 if (!developer_tools_enabled
) {
845 theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget
), event
);
850 return GTK_WIDGET_CLASS (empathy_theme_adium_parent_class
)->button_press_event (widget
, event
);
854 theme_adium_iface_init (EmpathyChatViewIface
*iface
)
856 iface
->append_message
= theme_adium_append_message
;
857 iface
->append_event
= theme_adium_append_event
;
858 iface
->scroll
= theme_adium_scroll
;
859 iface
->scroll_down
= theme_adium_scroll_down
;
860 iface
->get_has_selection
= theme_adium_get_has_selection
;
861 iface
->clear
= theme_adium_clear
;
862 iface
->find_previous
= theme_adium_find_previous
;
863 iface
->find_next
= theme_adium_find_next
;
864 iface
->find_abilities
= theme_adium_find_abilities
;
865 iface
->highlight
= theme_adium_highlight
;
866 iface
->copy_clipboard
= theme_adium_copy_clipboard
;
870 theme_adium_load_finished_cb (WebKitWebView
*view
,
871 WebKitWebFrame
*frame
,
874 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (view
);
875 EmpathyChatView
*chat_view
= EMPATHY_CHAT_VIEW (view
);
877 DEBUG ("Page loaded");
878 priv
->page_loaded
= TRUE
;
880 /* Display queued messages */
881 priv
->message_queue
= g_list_reverse (priv
->message_queue
);
882 while (priv
->message_queue
) {
883 EmpathyMessage
*message
= priv
->message_queue
->data
;
885 theme_adium_append_message (chat_view
, message
);
886 priv
->message_queue
= g_list_remove (priv
->message_queue
, message
);
887 g_object_unref (message
);
892 theme_adium_finalize (GObject
*object
)
894 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (object
);
896 empathy_adium_data_unref (priv
->data
);
898 empathy_conf_notify_remove (empathy_conf_get (),
899 priv
->notify_enable_webkit_developer_tools_id
);
901 G_OBJECT_CLASS (empathy_theme_adium_parent_class
)->finalize (object
);
905 theme_adium_dispose (GObject
*object
)
907 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (object
);
909 if (priv
->smiley_manager
) {
910 g_object_unref (priv
->smiley_manager
);
911 priv
->smiley_manager
= NULL
;
914 if (priv
->last_contact
) {
915 g_object_unref (priv
->last_contact
);
916 priv
->last_contact
= NULL
;
919 if (priv
->inspector_window
) {
920 gtk_widget_destroy (priv
->inspector_window
);
921 priv
->inspector_window
= NULL
;
924 G_OBJECT_CLASS (empathy_theme_adium_parent_class
)->dispose (object
);
928 theme_adium_inspector_show_window_cb (WebKitWebInspector
*inspector
,
929 EmpathyThemeAdium
*theme
)
931 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
933 if (priv
->inspector_window
) {
934 gtk_widget_show_all (priv
->inspector_window
);
941 theme_adium_inspector_close_window_cb (WebKitWebInspector
*inspector
,
942 EmpathyThemeAdium
*theme
)
944 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
946 if (priv
->inspector_window
) {
947 gtk_widget_hide (priv
->inspector_window
);
953 static WebKitWebView
*
954 theme_adium_inspect_web_view_cb (WebKitWebInspector
*inspector
,
955 WebKitWebView
*web_view
,
956 EmpathyThemeAdium
*theme
)
958 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (theme
);
959 GtkWidget
*scrolled_window
;
960 GtkWidget
*inspector_web_view
;
962 if (!priv
->inspector_window
) {
963 /* Create main window */
964 priv
->inspector_window
= gtk_window_new (GTK_WINDOW_TOPLEVEL
);
965 gtk_window_set_default_size (GTK_WINDOW (priv
->inspector_window
),
967 g_signal_connect (priv
->inspector_window
, "delete-event",
968 G_CALLBACK (gtk_widget_hide_on_delete
), NULL
);
970 /* Pack a scrolled window */
971 scrolled_window
= gtk_scrolled_window_new (NULL
, NULL
);
972 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window
),
973 GTK_POLICY_AUTOMATIC
,
974 GTK_POLICY_AUTOMATIC
);
975 gtk_container_add (GTK_CONTAINER (priv
->inspector_window
),
977 gtk_widget_show (scrolled_window
);
979 /* Pack a webview in the scrolled window. That webview will be
980 * used to render the inspector tool. */
981 inspector_web_view
= webkit_web_view_new ();
982 gtk_container_add (GTK_CONTAINER (scrolled_window
),
984 gtk_widget_show (scrolled_window
);
986 return WEBKIT_WEB_VIEW (inspector_web_view
);
992 static PangoFontDescription
*
993 theme_adium_get_default_font (void)
995 GConfClient
*gconf_client
;
996 PangoFontDescription
*pango_fd
;
997 gchar
*gconf_font_family
;
999 gconf_client
= gconf_client_get_default ();
1000 if (gconf_client
== NULL
) {
1003 gconf_font_family
= gconf_client_get_string (gconf_client
,
1004 EMPATHY_GCONF_FONT_KEY_NAME
,
1006 if (gconf_font_family
== NULL
) {
1007 g_object_unref (gconf_client
);
1010 pango_fd
= pango_font_description_from_string (gconf_font_family
);
1011 g_free (gconf_font_family
);
1012 g_object_unref (gconf_client
);
1017 theme_adium_set_webkit_font (WebKitWebSettings
*w_settings
,
1021 g_object_set (w_settings
, "default-font-family", name
, NULL
);
1022 g_object_set (w_settings
, "default-font-size", size
, NULL
);
1026 theme_adium_set_default_font (WebKitWebSettings
*w_settings
)
1028 PangoFontDescription
*default_font_desc
;
1029 GdkScreen
*current_screen
;
1031 gint pango_font_size
= 0;
1033 default_font_desc
= theme_adium_get_default_font ();
1034 if (default_font_desc
== NULL
)
1036 pango_font_size
= pango_font_description_get_size (default_font_desc
)
1038 if (pango_font_description_get_size_is_absolute (default_font_desc
)) {
1039 current_screen
= gdk_screen_get_default ();
1040 if (current_screen
!= NULL
) {
1041 dpi
= gdk_screen_get_resolution (current_screen
);
1043 dpi
= BORING_DPI_DEFAULT
;
1045 pango_font_size
= (gint
) (pango_font_size
/ (dpi
/ 72));
1047 theme_adium_set_webkit_font (w_settings
,
1048 pango_font_description_get_family (default_font_desc
),
1050 pango_font_description_free (default_font_desc
);
1054 theme_adium_constructed (GObject
*object
)
1056 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (object
);
1058 const gchar
*font_family
= NULL
;
1060 WebKitWebView
*webkit_view
= WEBKIT_WEB_VIEW (object
);
1061 WebKitWebSettings
*webkit_settings
;
1062 WebKitWebInspector
*webkit_inspector
;
1064 /* Set default settings */
1065 font_family
= tp_asv_get_string (priv
->data
->info
, "DefaultFontFamily");
1066 font_size
= tp_asv_get_int32 (priv
->data
->info
, "DefaultFontSize", NULL
);
1067 webkit_settings
= webkit_web_view_get_settings (webkit_view
);
1069 if (font_family
&& font_size
) {
1070 theme_adium_set_webkit_font (webkit_settings
, font_family
, font_size
);
1072 theme_adium_set_default_font (webkit_settings
);
1075 /* Setup webkit inspector */
1076 webkit_inspector
= webkit_web_view_get_inspector (webkit_view
);
1077 g_signal_connect (webkit_inspector
, "inspect-web-view",
1078 G_CALLBACK (theme_adium_inspect_web_view_cb
),
1080 g_signal_connect (webkit_inspector
, "show-window",
1081 G_CALLBACK (theme_adium_inspector_show_window_cb
),
1083 g_signal_connect (webkit_inspector
, "close-window",
1084 G_CALLBACK (theme_adium_inspector_close_window_cb
),
1088 basedir_uri
= g_strconcat ("file://", priv
->data
->basedir
, NULL
);
1089 webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object
),
1090 priv
->data
->template_html
,
1092 g_free (basedir_uri
);
1096 theme_adium_get_property (GObject
*object
,
1101 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (object
);
1104 case PROP_ADIUM_DATA
:
1105 g_value_set_boxed (value
, priv
->data
);
1108 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
1114 theme_adium_set_property (GObject
*object
,
1116 const GValue
*value
,
1119 EmpathyThemeAdiumPriv
*priv
= GET_PRIV (object
);
1122 case PROP_ADIUM_DATA
:
1123 g_assert (priv
->data
== NULL
);
1124 priv
->data
= g_value_dup_boxed (value
);
1127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
1133 empathy_theme_adium_class_init (EmpathyThemeAdiumClass
*klass
)
1135 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
1136 GtkWidgetClass
* widget_class
= GTK_WIDGET_CLASS (klass
);
1138 object_class
->finalize
= theme_adium_finalize
;
1139 object_class
->dispose
= theme_adium_dispose
;
1140 object_class
->constructed
= theme_adium_constructed
;
1141 object_class
->get_property
= theme_adium_get_property
;
1142 object_class
->set_property
= theme_adium_set_property
;
1144 widget_class
->button_press_event
= theme_adium_button_press_event
;
1146 g_object_class_install_property (object_class
,
1148 g_param_spec_boxed ("adium-data",
1150 "Data for the adium theme",
1151 EMPATHY_TYPE_ADIUM_DATA
,
1152 G_PARAM_CONSTRUCT_ONLY
|
1154 G_PARAM_STATIC_STRINGS
));
1156 g_type_class_add_private (object_class
, sizeof (EmpathyThemeAdiumPriv
));
1160 empathy_theme_adium_init (EmpathyThemeAdium
*theme
)
1162 EmpathyThemeAdiumPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (theme
,
1163 EMPATHY_TYPE_THEME_ADIUM
, EmpathyThemeAdiumPriv
);
1167 priv
->smiley_manager
= empathy_smiley_manager_dup_singleton ();
1169 g_signal_connect (theme
, "load-finished",
1170 G_CALLBACK (theme_adium_load_finished_cb
),
1172 g_signal_connect (theme
, "navigation-policy-decision-requested",
1173 G_CALLBACK (theme_adium_navigation_policy_decision_requested_cb
),
1176 priv
->notify_enable_webkit_developer_tools_id
=
1177 empathy_conf_notify_add (empathy_conf_get (),
1178 EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS
,
1179 theme_adium_notify_enable_webkit_developer_tools_cb
,
1182 theme_adium_update_enable_webkit_developer_tools (theme
);
1186 empathy_theme_adium_new (EmpathyAdiumData
*data
)
1188 g_return_val_if_fail (data
!= NULL
, NULL
);
1190 return g_object_new (EMPATHY_TYPE_THEME_ADIUM
,
1196 empathy_adium_path_is_valid (const gchar
*path
)
1201 /* The theme is not valid if there is no Info.plist */
1202 file
= g_build_filename (path
, "Contents", "Info.plist",
1204 ret
= g_file_test (file
, G_FILE_TEST_EXISTS
);
1210 /* We ship a default Template.html as fallback if there is any problem
1211 * with the one inside the theme. The only other required file is
1212 * Content.html for incoming messages (outgoing fallback to use
1214 file
= g_build_filename (path
, "Contents", "Resources", "Incoming",
1215 "Content.html", NULL
);
1216 ret
= g_file_test (file
, G_FILE_TEST_EXISTS
);
1223 empathy_adium_info_new (const gchar
*path
)
1227 GHashTable
*info
= NULL
;
1229 g_return_val_if_fail (empathy_adium_path_is_valid (path
), NULL
);
1231 file
= g_build_filename (path
, "Contents", "Info.plist", NULL
);
1232 value
= empathy_plist_parse_from_file (file
);
1238 info
= g_value_dup_boxed (value
);
1239 tp_g_value_slice_free (value
);
1241 /* Insert the theme's path into the hash table,
1242 * keys have to be dupped */
1243 tp_asv_set_string (info
, g_strdup ("path"), path
);
1249 empathy_adium_data_get_type (void)
1251 static GType type_id
= 0;
1255 type_id
= g_boxed_type_register_static ("EmpathyAdiumData",
1256 (GBoxedCopyFunc
) empathy_adium_data_ref
,
1257 (GBoxedFreeFunc
) empathy_adium_data_unref
);
1264 empathy_adium_data_new_with_info (const gchar
*path
, GHashTable
*info
)
1266 EmpathyAdiumData
*data
;
1268 gchar
*template_html
= NULL
;
1270 gchar
*footer_html
= NULL
;
1273 gchar
**strv
= NULL
;
1278 g_return_val_if_fail (empathy_adium_path_is_valid (path
), NULL
);
1280 data
= g_slice_new0 (EmpathyAdiumData
);
1281 data
->ref_count
= 1;
1282 data
->path
= g_strdup (path
);
1283 data
->basedir
= g_strconcat (path
, G_DIR_SEPARATOR_S
"Contents"
1284 G_DIR_SEPARATOR_S
"Resources" G_DIR_SEPARATOR_S
, NULL
);
1285 data
->info
= g_hash_table_ref (info
);
1287 /* Load html files */
1288 file
= g_build_filename (data
->basedir
, "Incoming", "Content.html", NULL
);
1289 g_file_get_contents (file
, &data
->in_content_html
, &data
->in_content_len
, NULL
);
1292 file
= g_build_filename (data
->basedir
, "Incoming", "NextContent.html", NULL
);
1293 g_file_get_contents (file
, &data
->in_nextcontent_html
, &data
->in_nextcontent_len
, NULL
);
1296 file
= g_build_filename (data
->basedir
, "Incoming", "Context.html", NULL
);
1297 g_file_get_contents (file
, &data
->in_context_html
, &data
->in_context_len
, NULL
);
1300 file
= g_build_filename (data
->basedir
, "Incoming", "NextContext.html", NULL
);
1301 g_file_get_contents (file
, &data
->in_nextcontext_html
, &data
->in_nextcontext_len
, NULL
);
1304 file
= g_build_filename (data
->basedir
, "Outgoing", "Content.html", NULL
);
1305 g_file_get_contents (file
, &data
->out_content_html
, &data
->out_content_len
, NULL
);
1308 file
= g_build_filename (data
->basedir
, "Outgoing", "NextContent.html", NULL
);
1309 g_file_get_contents (file
, &data
->out_nextcontent_html
, &data
->out_nextcontent_len
, NULL
);
1312 file
= g_build_filename (data
->basedir
, "Outgoing", "Context.html", NULL
);
1313 g_file_get_contents (file
, &data
->out_context_html
, &data
->out_context_len
, NULL
);
1316 file
= g_build_filename (data
->basedir
, "Outgoing", "NextContext.html", NULL
);
1317 g_file_get_contents (file
, &data
->out_nextcontext_html
, &data
->out_nextcontext_len
, NULL
);
1320 file
= g_build_filename (data
->basedir
, "Status.html", NULL
);
1321 g_file_get_contents (file
, &data
->status_html
, &data
->status_len
, NULL
);
1324 file
= g_build_filename (data
->basedir
, "Footer.html", NULL
);
1325 g_file_get_contents (file
, &footer_html
, &footer_len
, NULL
);
1328 file
= g_build_filename (data
->basedir
, "Incoming", "buddy_icon.png", NULL
);
1329 if (g_file_test (file
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_REGULAR
)) {
1330 data
->default_incoming_avatar_filename
= file
;
1335 file
= g_build_filename (data
->basedir
, "Outgoing", "buddy_icon.png", NULL
);
1336 if (g_file_test (file
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_REGULAR
)) {
1337 data
->default_outgoing_avatar_filename
= file
;
1342 css_path
= g_build_filename (data
->basedir
, "main.css", NULL
);
1344 /* There is 2 formats for Template.html: The old one has 4 parameters,
1345 * the new one has 5 parameters. */
1346 file
= g_build_filename (data
->basedir
, "Template.html", NULL
);
1347 if (g_file_get_contents (file
, &template_html
, &template_len
, NULL
)) {
1348 strv
= g_strsplit (template_html
, "%@", -1);
1349 len
= g_strv_length (strv
);
1353 if (len
!= 5 && len
!= 6) {
1354 /* Either the theme has no template or it don't have the good
1355 * number of parameters. Fallback to use our own template. */
1356 g_free (template_html
);
1359 file
= empathy_file_lookup ("Template.html", "data");
1360 g_file_get_contents (file
, &template_html
, &template_len
, NULL
);
1362 strv
= g_strsplit (template_html
, "%@", -1);
1363 len
= g_strv_length (strv
);
1366 /* Replace %@ with the needed information in the template html. */
1367 string
= g_string_sized_new (template_len
);
1368 g_string_append (string
, strv
[i
++]);
1369 g_string_append (string
, data
->basedir
);
1370 g_string_append (string
, strv
[i
++]);
1372 const gchar
*variant
;
1374 /* We include main.css by default */
1375 g_string_append_printf (string
, "@import url(\"%s\");", css_path
);
1376 g_string_append (string
, strv
[i
++]);
1377 variant
= tp_asv_get_string (data
->info
, "DefaultVariant");
1379 g_string_append (string
, "Variants/");
1380 g_string_append (string
, variant
);
1381 g_string_append (string
, ".css");
1384 /* FIXME: We should set main.css OR the variant css */
1385 g_string_append (string
, css_path
);
1387 g_string_append (string
, strv
[i
++]);
1388 g_string_append (string
, ""); /* We don't want header */
1389 g_string_append (string
, strv
[i
++]);
1390 /* FIXME: We should replace adium %macros% in footer */
1392 g_string_append (string
, footer_html
);
1394 g_string_append (string
, strv
[i
++]);
1395 data
->template_html
= g_string_free (string
, FALSE
);
1397 g_free (footer_html
);
1398 g_free (template_html
);
1406 empathy_adium_data_new (const gchar
*path
)
1408 EmpathyAdiumData
*data
;
1411 info
= empathy_adium_info_new (path
);
1412 data
= empathy_adium_data_new_with_info (path
, info
);
1413 g_hash_table_unref (info
);
1419 empathy_adium_data_ref (EmpathyAdiumData
*data
)
1421 g_return_val_if_fail (data
!= NULL
, NULL
);
1423 g_atomic_int_inc (&data
->ref_count
);
1429 empathy_adium_data_unref (EmpathyAdiumData
*data
)
1431 g_return_if_fail (data
!= NULL
);
1433 if (g_atomic_int_dec_and_test (&data
->ref_count
)) {
1434 g_free (data
->path
);
1435 g_free (data
->basedir
);
1436 g_free (data
->template_html
);
1437 g_free (data
->in_content_html
);
1438 g_free (data
->in_nextcontent_html
);
1439 g_free (data
->in_context_html
);
1440 g_free (data
->in_nextcontext_html
);
1441 g_free (data
->out_content_html
);
1442 g_free (data
->out_nextcontent_html
);
1443 g_free (data
->out_context_html
);
1444 g_free (data
->out_nextcontext_html
);
1445 g_free (data
->default_avatar_filename
);
1446 g_free (data
->default_incoming_avatar_filename
);
1447 g_free (data
->default_outgoing_avatar_filename
);
1448 g_free (data
->status_html
);
1449 g_hash_table_unref (data
->info
);
1450 g_slice_free (EmpathyAdiumData
, data
);
1455 empathy_adium_data_get_info (EmpathyAdiumData
*data
)
1457 g_return_val_if_fail (data
!= NULL
, NULL
);
1463 empathy_adium_data_get_path (EmpathyAdiumData
*data
)
1465 g_return_val_if_fail (data
!= NULL
, NULL
);