#include "empathy-smiley-manager.h"
#include "empathy-ui-utils.h"
#include "empathy-plist.h"
-#include "empathy-string-parser.h"
#include "empathy-images.h"
+#include "empathy-webkit-utils.h"
#define DEBUG_FLAG EMPATHY_DEBUG_CHAT
#include <libempathy/empathy-debug.h>
gint64 last_timestamp;
gboolean last_is_backlog;
guint pages_loading;
- /* Queue of GValue* containing an EmpathyMessage or string */
+ /* Queue of QueuedItem*s containing an EmpathyMessage or string */
GQueue message_queue;
- /* Queue of owned gchar* of message token to remove unread
+ /* Queue of guint32 of pending message id to remove unread
* marker for when we lose focus. */
GQueue acked_messages;
GtkWidget *inspector_window;
+
GSettings *gsettings_chat;
+ GSettings *gsettings_desktop;
+
gboolean has_focus;
gboolean has_unread_message;
gboolean allow_scrolling;
G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW,
theme_adium_iface_init));
+enum {
+ QUEUED_EVENT,
+ QUEUED_MESSAGE,
+ QUEUED_EDIT
+};
+
+typedef struct {
+ guint type;
+ EmpathyMessage *msg;
+ char *str;
+} QueuedItem;
+
+static QueuedItem *
+queue_item (GQueue *queue,
+ guint type,
+ EmpathyMessage *msg,
+ const char *str)
+{
+ QueuedItem *item = g_slice_new0 (QueuedItem);
+
+ item->type = type;
+ if (msg != NULL)
+ item->msg = g_object_ref (msg);
+ item->str = g_strdup (str);
+
+ g_queue_push_tail (queue, item);
+
+ return item;
+}
+
+static void
+free_queued_item (QueuedItem *item)
+{
+ tp_clear_object (&item->msg);
+ g_free (item->str);
+
+ g_slice_free (QueuedItem, item);
+}
+
static void
theme_adium_update_enable_webkit_developer_tools (EmpathyThemeAdium *theme)
{
return TRUE;
}
-static void
-theme_adium_copy_address_cb (GtkMenuItem *menuitem,
- gpointer user_data)
-{
- WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
- gchar *uri;
- GtkClipboard *clipboard;
-
- g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
- gtk_clipboard_set_text (clipboard, uri, -1);
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
- gtk_clipboard_set_text (clipboard, uri, -1);
-
- g_free (uri);
-}
-
-static void
-theme_adium_open_address_cb (GtkMenuItem *menuitem,
- gpointer user_data)
-{
- WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
- gchar *uri;
-
- g_object_get (G_OBJECT (hit_test_result), "link-uri", &uri, NULL);
-
- empathy_url_show (GTK_WIDGET (menuitem), uri);
-
- g_free (uri);
-}
-
/* Replace each %@ in format with string passed in args */
static gchar *
string_with_format (const gchar *format,
g_free (template);
}
-static void
-theme_adium_match_newline (const gchar *text,
- gssize len,
- EmpathyStringReplace replace_func,
- EmpathyStringParser *sub_parsers,
- gpointer user_data)
-{
- GString *string = user_data;
- gint i;
- gint prev = 0;
-
- if (len < 0) {
- len = G_MAXSSIZE;
- }
-
- /* Replace \n by <br/> */
- for (i = 0; i < len && text[i] != '\0'; i++) {
- if (text[i] == '\n') {
- empathy_string_parser_substr (text + prev,
- i - prev, sub_parsers,
- user_data);
- g_string_append (string, "<br/>");
- prev = i + 1;
- }
- }
- empathy_string_parser_substr (text + prev, i - prev,
- sub_parsers, user_data);
-}
-
-static void
-theme_adium_replace_smiley (const gchar *text,
- gssize len,
- gpointer match_data,
- gpointer user_data)
-{
- EmpathySmileyHit *hit = match_data;
- GString *string = user_data;
-
- /* Replace smiley by a <img/> tag */
- g_string_append_printf (string,
- "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>",
- hit->path, (int)len, text, (int)len, text);
-}
-
-static EmpathyStringParser string_parsers[] = {
- {empathy_string_match_link, empathy_string_replace_link},
- {theme_adium_match_newline, NULL},
- {empathy_string_match_all, empathy_string_replace_escaped},
- {NULL, NULL}
-};
-
-static EmpathyStringParser string_parsers_with_smiley[] = {
- {empathy_string_match_link, empathy_string_replace_link},
- {empathy_string_match_smiley, theme_adium_replace_smiley},
- {theme_adium_match_newline, NULL},
- {empathy_string_match_all, empathy_string_replace_escaped},
- {NULL, NULL}
-};
-
static gchar *
theme_adium_parse_body (EmpathyThemeAdium *self,
const gchar *text,
GString *string;
/* Check if we have to parse smileys */
- if (g_settings_get_boolean (priv->gsettings_chat,
- EMPATHY_PREFS_CHAT_SHOW_SMILEYS))
- parsers = string_parsers_with_smiley;
- else
- parsers = string_parsers;
+ parsers = empathy_webkit_get_string_parser (
+ g_settings_get_boolean (priv->gsettings_chat,
+ EMPATHY_PREFS_CHAT_SHOW_SMILEYS));
/* Parse text and construct string with links and smileys replaced
* by html tags. Also escape text to make sure html code is
const gchar *service_name,
const gchar *message_classes,
gint64 timestamp,
- gboolean is_backlog)
+ gboolean is_backlog,
+ gboolean outgoing)
{
EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
GString *string;
* Incoming/SenderColors.txt it will be used instead of
* the default colors.
*/
- if (contact_id != NULL) {
+
+ /* Ensure we always use the same color when sending messages
+ * (bgo #658821) */
+ if (outgoing) {
+ replace = "inherit";
+ } else if (contact_id != NULL) {
guint hash = g_str_hash (contact_id);
replace = colors[hash % G_N_ELEMENTS (colors)];
}
theme_adium_append_html (theme, "appendMessage",
priv->data->status_html, escaped, NULL, NULL, NULL,
NULL, "event",
- empathy_time_get_current (), FALSE);
+ empathy_time_get_current (), FALSE, FALSE);
/* There is no last contact */
if (priv->last_contact) {
gboolean action;
if (priv->pages_loading != 0) {
- GValue *value = tp_g_value_slice_new (EMPATHY_TYPE_MESSAGE);
- g_value_set_object (value, msg);
- g_queue_push_tail (&priv->message_queue, value);
+ queue_item (&priv->message_queue, QUEUED_MESSAGE, msg, NULL);
return;
}
body_escaped = theme_adium_parse_body (theme,
empathy_message_get_body (msg),
empathy_message_get_token (msg));
- name = empathy_contact_get_alias (sender);
+ name = empathy_contact_get_logged_alias (sender);
contact_id = empathy_contact_get_id (sender);
action = (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
* message later. */
tp_msg = empathy_message_get_tp_message (msg);
if (tp_msg != NULL) {
- gchar *tmp = tp_escape_as_identifier (
- tp_message_get_token (tp_msg));
- g_string_append_printf (message_classes,
- " x-empathy-message-id-%s", tmp);
- g_free (tmp);
+ guint32 id;
+ gboolean valid;
+
+ id = tp_message_get_pending_message_id (tp_msg, &valid);
+ if (valid) {
+ g_string_append_printf (message_classes,
+ " x-empathy-message-id-%u", id);
+ }
}
/* Define javascript function to use */
theme_adium_append_html (theme, func, html, body_escaped,
avatar_filename, name, contact_id,
service_name, message_classes->str,
- timestamp, is_backlog);
+ timestamp, is_backlog, empathy_contact_is_user (sender));
/* Keep the sender of the last displayed message */
if (priv->last_contact) {
gchar *str_escaped;
if (priv->pages_loading != 0) {
- g_queue_push_tail (&priv->message_queue,
- tp_g_value_slice_new_string (str));
+ queue_item (&priv->message_queue, QUEUED_EVENT, NULL, str);
return;
}
g_free (str_escaped);
}
+static void
+theme_adium_append_event_markup (EmpathyChatView *view,
+ const gchar *markup_text,
+ const gchar *fallback_text)
+{
+ theme_adium_append_event_escaped (view, markup_text);
+}
+
+static void
+theme_adium_edit_message (EmpathyChatView *view,
+ EmpathyMessage *message)
+{
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
+ WebKitDOMDocument *doc;
+ WebKitDOMElement *span;
+ gchar *id, *parsed_body;
+ gchar *tooltip, *timestamp;
+ GtkIconInfo *icon_info;
+ GError *error = NULL;
+
+ if (priv->pages_loading != 0) {
+ queue_item (&priv->message_queue, QUEUED_EDIT, message, NULL);
+ return;
+ }
+
+ id = g_strdup_printf ("message-token-%s",
+ empathy_message_get_supersedes (message));
+ /* we don't pass a token here, because doing so will return another
+ * <span> element, and we don't want nested <span> elements */
+ parsed_body = theme_adium_parse_body (EMPATHY_THEME_ADIUM (view),
+ empathy_message_get_body (message), NULL);
+
+ /* find the element */
+ doc = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ span = webkit_dom_document_get_element_by_id (doc, id);
+
+ if (span == NULL) {
+ DEBUG ("Failed to find id '%s'", id);
+ goto except;
+ }
+
+ if (!WEBKIT_DOM_IS_HTML_ELEMENT (span)) {
+ DEBUG ("Not a HTML element");
+ goto except;
+ }
+
+ /* update the HTML */
+ webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (span),
+ parsed_body, &error);
+
+ if (error != NULL) {
+ DEBUG ("Error setting new inner-HTML: %s", error->message);
+ g_error_free (error);
+ goto except;
+ }
+
+ /* set a tooltip */
+ timestamp = empathy_time_to_string_local (
+ empathy_message_get_timestamp (message),
+ "%H:%M:%S");
+ tooltip = g_strdup_printf (_("Message edited at %s"), timestamp);
+
+ webkit_dom_html_element_set_title (WEBKIT_DOM_HTML_ELEMENT (span),
+ tooltip);
+
+ g_free (tooltip);
+ g_free (timestamp);
+
+ /* mark this message as edited */
+ icon_info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (),
+ EMPATHY_IMAGE_EDIT_MESSAGE, 16, 0);
+
+ if (icon_info != NULL) {
+ /* set the icon as a background image using CSS
+ * FIXME: the icon won't update in response to theme changes */
+ gchar *style = g_strdup_printf (
+ "background-image:url('%s');"
+ "background-repeat:no-repeat;"
+ "background-position:left center;"
+ "padding-left:19px;", /* 16px icon + 3px padding */
+ gtk_icon_info_get_filename (icon_info));
+
+ webkit_dom_element_set_attribute (span, "style", style, &error);
+
+ if (error != NULL) {
+ DEBUG ("Error setting element style: %s",
+ error->message);
+ g_clear_error (&error);
+ /* not fatal */
+ }
+
+ g_free (style);
+ gtk_icon_info_free (icon_info);
+ }
+
+ goto finally;
+
+except:
+ DEBUG ("Could not find message to edit with: %s",
+ empathy_message_get_body (message));
+
+finally:
+ g_free (id);
+ g_free (parsed_body);
+}
+
static void
theme_adium_scroll (EmpathyChatView *view,
gboolean allow_scrolling)
static void
theme_adium_remove_mark_from_message (EmpathyThemeAdium *self,
- const gchar *token)
+ guint32 id)
{
WebKitDOMDocument *dom;
WebKitDOMNodeList *nodes;
- gchar *class, *tmp;
+ gchar *class;
GError *error = NULL;
dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (self));
return;
}
- tmp = tp_escape_as_identifier (token);
- class = g_strdup_printf (".x-empathy-message-id-%s", tmp);
- g_free (tmp);
+ class = g_strdup_printf (".x-empathy-message-id-%u", id);
/* Get all nodes with focus class */
nodes = webkit_dom_document_query_selector_all (dom, class, &error);
gpointer user_data)
{
EmpathyThemeAdium *self = user_data;
- gchar *token = data;
+ guint32 id = GPOINTER_TO_UINT (data);
- theme_adium_remove_mark_from_message (self, token);
- g_free (token);
+ theme_adium_remove_mark_from_message (self, id);
}
static void
EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
TpMessage *tp_msg;
+ guint32 id;
+ gboolean valid;
tp_msg = empathy_message_get_tp_message (message);
return;
}
+ id = tp_message_get_pending_message_id (tp_msg, &valid);
+ if (!valid) {
+ g_warning ("Acknoledged message doesn't have a pending ID");
+ return;
+ }
+
/* We only want to actually remove the unread marker if the
* view doesn't have focus. If we did it all the time we would
* never see the unread markers, ever! So, we'll queue these
* up, and when we lose focus, we'll remove the markers. */
if (priv->has_focus) {
g_queue_push_tail (&priv->acked_messages,
- g_strdup (tp_message_get_token (tp_msg)));
+ GUINT_TO_POINTER (id));
return;
}
- theme_adium_remove_mark_from_message (self,
- tp_message_get_token (tp_msg));
-}
-
-static void
-theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
-{
- WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
-
- g_object_unref (hit_test_result);
-}
-
-static void
-theme_adium_context_menu_for_event (EmpathyThemeAdium *theme, GdkEventButton *event)
-{
- WebKitWebView *view = WEBKIT_WEB_VIEW (theme);
- WebKitHitTestResult *hit_test_result;
- WebKitHitTestResultContext context;
- GtkWidget *menu;
- GtkWidget *item;
-
- hit_test_result = webkit_web_view_get_hit_test_result (view, event);
- g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
-
- /* The menu */
- menu = empathy_context_menu_new (GTK_WIDGET (view));
-
- /* Select all item */
- item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (webkit_web_view_select_all),
- view);
-
- /* Copy menu item */
- if (webkit_web_view_can_copy_clipboard (view)) {
- item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (webkit_web_view_copy_clipboard),
- view);
- }
-
- /* Clear menu item */
- item = gtk_separator_menu_item_new ();
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- g_signal_connect_swapped (item, "activate",
- G_CALLBACK (empathy_chat_view_clear),
- view);
-
- /* We will only add the following menu items if we are
- * right-clicking a link */
- if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
- /* Separator */
- item = gtk_separator_menu_item_new ();
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- /* Copy Link Address menu item */
- item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
- g_signal_connect (item, "activate",
- G_CALLBACK (theme_adium_copy_address_cb),
- hit_test_result);
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
-
- /* Open Link menu item */
- item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
- g_signal_connect (item, "activate",
- G_CALLBACK (theme_adium_open_address_cb),
- hit_test_result);
- gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
- }
-
- g_signal_connect (GTK_MENU_SHELL (menu), "selection-done",
- G_CALLBACK (theme_adium_context_menu_selection_done_cb),
- hit_test_result);
-
- /* Display the menu */
- gtk_widget_show_all (menu);
- gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
- event->button, event->time);
+ theme_adium_remove_mark_from_message (self, id);
}
static gboolean
* item ourselves, so we disable our customized menu
* if the developer extras are enabled. */
if (!developer_tools_enabled) {
- theme_adium_context_menu_for_event (EMPATHY_THEME_ADIUM (widget), event);
+ empathy_webkit_context_menu_for_event (
+ WEBKIT_WEB_VIEW (widget), event,
+ EMPATHY_WEBKIT_MENU_CLEAR);
return TRUE;
}
}
{
iface->append_message = theme_adium_append_message;
iface->append_event = theme_adium_append_event;
+ iface->append_event_markup = theme_adium_append_event_markup;
+ iface->edit_message = theme_adium_edit_message;
iface->scroll = theme_adium_scroll;
iface->scroll_down = theme_adium_scroll_down;
iface->get_has_selection = theme_adium_get_has_selection;
/* Display queued messages */
for (l = priv->message_queue.head; l != NULL; l = l->next) {
- GValue *value = l->data;
+ QueuedItem *item = l->data;
- if (G_VALUE_HOLDS_OBJECT (value)) {
- theme_adium_append_message (chat_view,
- g_value_get_object (value));
- } else {
- theme_adium_append_event (chat_view,
- g_value_get_string (value));
+ switch (item->type)
+ {
+ case QUEUED_MESSAGE:
+ theme_adium_append_message (chat_view, item->msg);
+ break;
+
+ case QUEUED_EDIT:
+ theme_adium_edit_message (chat_view, item->msg);
+ break;
+
+ case QUEUED_EVENT:
+ theme_adium_append_event (chat_view, item->str);
+ break;
}
- tp_g_value_slice_free (value);
+ free_queued_item (item);
}
+
g_queue_clear (&priv->message_queue);
}
EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
empathy_adium_data_unref (priv->data);
+
g_object_unref (priv->gsettings_chat);
+ g_object_unref (priv->gsettings_desktop);
G_OBJECT_CLASS (empathy_theme_adium_parent_class)->finalize (object);
}
}
if (priv->acked_messages.length > 0) {
- g_queue_foreach (&priv->acked_messages, (GFunc) g_free, NULL);
g_queue_clear (&priv->acked_messages);
}
return NULL;
}
-static PangoFontDescription *
-theme_adium_get_default_font (void)
-{
- GSettings *gsettings;
- PangoFontDescription *pango_fd;
- gchar *font_family;
-
- gsettings = g_settings_new (EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
-
- font_family = g_settings_get_string (gsettings,
- EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
-
- if (font_family == NULL)
- return NULL;
-
- pango_fd = pango_font_description_from_string (font_family);
- g_free (font_family);
- g_object_unref (gsettings);
- return pango_fd;
-}
-
-static void
-theme_adium_set_webkit_font (WebKitWebSettings *w_settings,
- const gchar *name,
- gint size)
-{
- g_object_set (w_settings, "default-font-family", name, NULL);
- g_object_set (w_settings, "default-font-size", size, NULL);
-}
-
-static void
-theme_adium_set_default_font (WebKitWebSettings *w_settings)
-{
- PangoFontDescription *default_font_desc;
- GdkScreen *current_screen;
- gdouble dpi = 0;
- gint pango_font_size = 0;
-
- default_font_desc = theme_adium_get_default_font ();
- if (default_font_desc == NULL)
- return ;
- pango_font_size = pango_font_description_get_size (default_font_desc)
- / PANGO_SCALE ;
- if (pango_font_description_get_size_is_absolute (default_font_desc)) {
- current_screen = gdk_screen_get_default ();
- if (current_screen != NULL) {
- dpi = gdk_screen_get_resolution (current_screen);
- } else {
- dpi = BORING_DPI_DEFAULT;
- }
- pango_font_size = (gint) (pango_font_size / (dpi / 72));
- }
- theme_adium_set_webkit_font (w_settings,
- pango_font_description_get_family (default_font_desc),
- pango_font_size);
- pango_font_description_free (default_font_desc);
-}
-
static void
theme_adium_constructed (GObject *object)
{
const gchar *font_family = NULL;
gint font_size = 0;
WebKitWebView *webkit_view = WEBKIT_WEB_VIEW (object);
- WebKitWebSettings *webkit_settings;
WebKitWebInspector *webkit_inspector;
/* Set default settings */
font_family = tp_asv_get_string (priv->data->info, "DefaultFontFamily");
font_size = tp_asv_get_int32 (priv->data->info, "DefaultFontSize", NULL);
- webkit_settings = webkit_web_view_get_settings (webkit_view);
if (font_family && font_size) {
- theme_adium_set_webkit_font (webkit_settings, font_family, font_size);
+ g_object_set (webkit_web_view_get_settings (webkit_view),
+ "default-font-family", font_family,
+ "default-font-size", font_size,
+ NULL);
} else {
- theme_adium_set_default_font (webkit_settings);
+ empathy_webkit_bind_font_setting (webkit_view,
+ priv->gsettings_desktop,
+ EMPATHY_PREFS_DESKTOP_INTERFACE_DOCUMENT_FONT_NAME);
}
/* Setup webkit inspector */
NULL);
priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
+ priv->gsettings_desktop = g_settings_new (
+ EMPATHY_PREFS_DESKTOP_INTERFACE_SCHEMA);
+
g_signal_connect (priv->gsettings_chat,
"changed::" EMPATHY_PREFS_CHAT_WEBKIT_DEVELOPER_TOOLS,
G_CALLBACK (theme_adium_notify_enable_webkit_developer_tools_cb),
g_object_notify (G_OBJECT (theme), "variant");
}
+void
+empathy_theme_adium_show_inspector (EmpathyThemeAdium *theme)
+{
+ WebKitWebView *web_view = WEBKIT_WEB_VIEW (theme);
+ WebKitWebInspector *inspector;
+
+ g_object_set (G_OBJECT (webkit_web_view_get_settings (web_view)),
+ "enable-developer-extras", TRUE,
+ NULL);
+
+ inspector = webkit_web_view_get_inspector (web_view);
+ webkit_web_inspector_show (inspector);
+}
+
gboolean
empathy_adium_path_is_valid (const gchar *path)
{
return g_strdup ("main.css");
}
- /* Verify the variant exists, fallback to the first one */
variants = empathy_adium_info_get_available_variants (info);
+ if (variants->len == 0)
+ return g_strdup ("main.css");
+
+ /* Verify the variant exists, fallback to the first one */
for (i = 0; i < variants->len; i++) {
if (!tp_strdiff (variant, g_ptr_array_index (variants, i))) {
break;