#include "config.h"
#include <string.h>
-#include <glib/gi18n.h>
+#include <glib/gi18n-lib.h>
#include <webkit/webkit.h>
#include <telepathy-glib/dbus.h>
#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;
- GList *message_queue;
+ /* Queue of QueuedItem*s containing an EmpathyMessage or string */
+ GQueue message_queue;
+ /* 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;
+ gchar *variant;
+ gboolean in_construction;
} EmpathyThemeAdiumPriv;
struct _EmpathyAdiumData {
gchar *default_avatar_filename;
gchar *default_incoming_avatar_filename;
gchar *default_outgoing_avatar_filename;
- gchar *template_html;
- gchar *content_html;
- gsize content_len;
GHashTable *info;
-
- /* Legacy themes */
- gchar *in_content_html;
- gsize in_content_len;
- gchar *in_context_html;
- gsize in_context_len;
- gchar *in_nextcontent_html;
- gsize in_nextcontent_len;
- gchar *in_nextcontext_html;
- gsize in_nextcontext_len;
- gchar *out_content_html;
- gsize out_content_len;
- gchar *out_context_html;
- gsize out_context_len;
- gchar *out_nextcontent_html;
- gsize out_nextcontent_len;
- gchar *out_nextcontext_html;
- gsize out_nextcontext_len;
- gchar *status_html;
- gsize status_len;
+ guint version;
+ gboolean custom_template;
+ /* gchar* -> gchar* both owned */
+ GHashTable *date_format_cache;
+
+ /* HTML bits */
+ const gchar *template_html;
+ const gchar *content_html;
+ const gchar *in_content_html;
+ const gchar *in_context_html;
+ const gchar *in_nextcontent_html;
+ const gchar *in_nextcontext_html;
+ const gchar *out_content_html;
+ const gchar *out_context_html;
+ const gchar *out_nextcontent_html;
+ const gchar *out_nextcontext_html;
+ const gchar *status_html;
+
+ /* Above html strings are pointers to strings stored in this array.
+ * We do this because of fallbacks, some htmls could be pointing the
+ * same string. */
+ GPtrArray *strings_to_free;
};
static void theme_adium_iface_init (EmpathyChatViewIface *iface);
+static gchar * adium_info_dup_path_for_variant (GHashTable *info, const gchar *variant);
enum {
PROP_0,
PROP_ADIUM_DATA,
+ PROP_VARIANT,
};
G_DEFINE_TYPE_WITH_CODE (EmpathyThemeAdium, empathy_theme_adium,
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;
+ gboolean should_highlight;
+} QueuedItem;
+
+static QueuedItem *
+queue_item (GQueue *queue,
+ guint type,
+ EmpathyMessage *msg,
+ const char *str,
+ gboolean should_highlight)
+{
+ QueuedItem *item = g_slice_new0 (QueuedItem);
+
+ item->type = type;
+ if (msg != NULL)
+ item->msg = g_object_ref (msg);
+ item->str = g_strdup (str);
+ item->should_highlight = should_highlight;
+
+ 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)
+/* Replace each %@ in format with string passed in args */
+static gchar *
+string_with_format (const gchar *format,
+ const gchar *first_string,
+ ...)
{
- 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);
+ va_list args;
+ const gchar *str;
+ GString *result;
- g_free (uri);
-}
+ va_start (args, first_string);
+ result = g_string_sized_new (strlen (format));
+ for (str = first_string; str != NULL; str = va_arg (args, const gchar *)) {
+ const gchar *next;
-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;
+ next = strstr (format, "%@");
+ if (next == NULL) {
+ break;
}
+
+ g_string_append_len (result, format, next - format);
+ g_string_append (result, str);
+ format = next + 2;
}
- empathy_string_parser_substr (text + prev, i - prev,
- sub_parsers, user_data);
+ g_string_append (result, format);
+ va_end (args);
+
+ return g_string_free (result, FALSE);
}
static void
-theme_adium_replace_smiley (const gchar *text,
- gssize len,
- gpointer match_data,
- gpointer user_data)
+theme_adium_load_template (EmpathyThemeAdium *theme)
{
- EmpathySmileyHit *hit = match_data;
- GString *string = user_data;
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
+ gchar *basedir_uri;
+ gchar *variant_path;
+ gchar *template;
- /* 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);
+ priv->pages_loading++;
+ basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
+ variant_path = adium_info_dup_path_for_variant (priv->data->info,
+ priv->variant);
+ template = string_with_format (priv->data->template_html,
+ variant_path, NULL);
+ webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (theme),
+ template, basedir_uri);
+ g_free (basedir_uri);
+ g_free (variant_path);
+ g_free (template);
}
-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)
+ const gchar *text,
+ const gchar *token)
{
EmpathyThemeAdiumPriv *priv = GET_PRIV (self);
EmpathyStringParser *parsers;
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
* displayed verbatim. */
string = g_string_sized_new (strlen (text));
+
+ /* wrap this in HTML that allows us to find the message for later
+ * editing */
+ if (!tp_str_empty (token))
+ g_string_append_printf (string,
+ "<span id=\"message-token-%s\">",
+ token);
+
empathy_string_parser_substr (text, -1, parsers, string);
+ if (!tp_str_empty (token))
+ g_string_append (string, "</span>");
+
/* Wrap body in order to make tabs and multiple spaces displayed
* properly. See bug #625745. */
g_string_prepend (string, "<div style=\"display: inline; "
"yellowgreen",
};
+static const gchar *
+nsdate_to_strftime (EmpathyAdiumData *data, const gchar *nsdate)
+{
+ /* Convert from NSDateFormatter (http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/)
+ * to strftime supported by g_date_time_format.
+ * FIXME: table is incomplete, doc of g_date_time_format has a table of
+ * supported tags.
+ * FIXME: g_date_time_format in GLib 2.28 does 0 padding by default, but
+ * in 2.29.x we have to explictely request padding with %0x */
+ static const gchar *convert_table[] = {
+ "a", "%p", // AM/PM
+ "A", NULL, // 0~86399999 (Millisecond of Day)
+
+ "cccc", "%A", // Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
+ "ccc", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
+ "cc", "%u", // 1~7 (Day of Week)
+ "c", "%u", // 1~7 (Day of Week)
+
+ "dd", "%d", // 1~31 (0 padded Day of Month)
+ "d", "%d", // 1~31 (0 padded Day of Month)
+ "D", "%j", // 1~366 (0 padded Day of Year)
+
+ "e", "%u", // 1~7 (0 padded Day of Week)
+ "EEEE", "%A", // Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
+ "EEE", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
+ "EE", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
+ "E", "%a", // Sun/Mon/Tue/Wed/Thu/Fri/Sat
+
+ "F", NULL, // 1~5 (0 padded Week of Month, first day of week = Monday)
+
+ "g", NULL, // Julian Day Number (number of days since 4713 BC January 1)
+ "GGGG", NULL, // Before Christ/Anno Domini
+ "GGG", NULL, // BC/AD (Era Designator Abbreviated)
+ "GG", NULL, // BC/AD (Era Designator Abbreviated)
+ "G", NULL, // BC/AD (Era Designator Abbreviated)
+
+ "h", "%I", // 1~12 (0 padded Hour (12hr))
+ "H", "%H", // 0~23 (0 padded Hour (24hr))
+
+ "k", NULL, // 1~24 (0 padded Hour (24hr)
+ "K", NULL, // 0~11 (0 padded Hour (12hr))
+
+ "LLLL", "%B", // January/February/March/April/May/June/July/August/September/October/November/December
+ "LLL", "%b", // Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
+ "LL", "%m", // 1~12 (0 padded Month)
+ "L", "%m", // 1~12 (0 padded Month)
+
+ "m", "%M", // 0~59 (0 padded Minute)
+ "MMMM", "%B", // January/February/March/April/May/June/July/August/September/October/November/December
+ "MMM", "%b", // Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
+ "MM", "%m", // 1~12 (0 padded Month)
+ "M", "%m", // 1~12 (0 padded Month)
+
+ "qqqq", NULL, // 1st quarter/2nd quarter/3rd quarter/4th quarter
+ "qqq", NULL, // Q1/Q2/Q3/Q4
+ "qq", NULL, // 1~4 (0 padded Quarter)
+ "q", NULL, // 1~4 (0 padded Quarter)
+ "QQQQ", NULL, // 1st quarter/2nd quarter/3rd quarter/4th quarter
+ "QQQ", NULL, // Q1/Q2/Q3/Q4
+ "QQ", NULL, // 1~4 (0 padded Quarter)
+ "Q", NULL, // 1~4 (0 padded Quarter)
+
+ "s", "%S", // 0~59 (0 padded Second)
+ "S", NULL, // (rounded Sub-Second)
+
+ "u", "%Y", // (0 padded Year)
+
+ "vvvv", "%Z", // (General GMT Timezone Name)
+ "vvv", "%Z", // (General GMT Timezone Abbreviation)
+ "vv", "%Z", // (General GMT Timezone Abbreviation)
+ "v", "%Z", // (General GMT Timezone Abbreviation)
+
+ "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)
+ "W", NULL, // 1~5 (0 padded Week of Month, 1st day of week = Sunday)
+
+ "yyyy", "%Y", // (Full Year)
+ "yyy", "%y", // (2 Digits Year)
+ "yy", "%y", // (2 Digits Year)
+ "y", "%Y", // (Full Year)
+ "YYYY", NULL, // (Full Year, starting from the Sunday of the 1st week of year)
+ "YYY", NULL, // (2 Digits Year, starting from the Sunday of the 1st week of year)
+ "YY", NULL, // (2 Digits Year, starting from the Sunday of the 1st week of year)
+ "Y", NULL, // (Full Year, starting from the Sunday of the 1st week of year)
+
+ "zzzz", NULL, // (Specific GMT Timezone Name)
+ "zzz", NULL, // (Specific GMT Timezone Abbreviation)
+ "zz", NULL, // (Specific GMT Timezone Abbreviation)
+ "z", NULL, // (Specific GMT Timezone Abbreviation)
+ "Z", "%z", // +0000 (RFC 822 Timezone)
+ };
+ const gchar *str;
+ GString *string;
+ guint i, j;
+
+ if (nsdate == NULL) {
+ return NULL;
+ }
+
+ str = g_hash_table_lookup (data->date_format_cache, nsdate);
+ if (str != NULL) {
+ return str;
+ }
+
+ /* Copy nsdate into string, replacing occurences of NSDateFormatter tags
+ * by corresponding strftime tag. */
+ string = g_string_sized_new (strlen (nsdate));
+ for (i = 0; nsdate[i] != '\0'; i++) {
+ gboolean found = FALSE;
+
+ /* even indexes are NSDateFormatter tag, odd indexes are the
+ * corresponding strftime tag */
+ for (j = 0; j < G_N_ELEMENTS (convert_table); j += 2) {
+ if (g_str_has_prefix (nsdate + i, convert_table[j])) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (found) {
+ /* If we don't have a replacement, just ignore that tag */
+ if (convert_table[j + 1] != NULL) {
+ g_string_append (string, convert_table[j + 1]);
+ }
+ i += strlen (convert_table[j]) - 1;
+ } else {
+ g_string_append_c (string, nsdate[i]);
+ }
+ }
+
+ DEBUG ("Date format converted '%s' → '%s'", nsdate, string->str);
+
+ /* The cache takes ownership of string->str */
+ g_hash_table_insert (data->date_format_cache, g_strdup (nsdate), string->str);
+ return g_string_free (string, FALSE);
+}
+
+
static void
theme_adium_append_html (EmpathyThemeAdium *theme,
const gchar *func,
- const gchar *html, gsize len,
+ const gchar *html,
const gchar *message,
const gchar *avatar_filename,
const gchar *name,
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;
const gchar *cur = NULL;
gchar *script;
/* Make some search-and-replace in the html code */
- string = g_string_sized_new (len + strlen (message));
+ string = g_string_sized_new (strlen (html) + strlen (message));
g_string_append_printf (string, "%s(\"", func);
for (cur = html; *cur != '\0'; cur++) {
const gchar *replace = NULL;
* Incoming/SenderColors.txt it will be used instead of
* the default colors.
*/
- guint hash = g_str_hash (contact_id);
- replace = colors[hash % G_N_ELEMENTS (colors)];
+ /* 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)];
+ }
} else if (theme_adium_match (&cur, "%senderStatusIcon%")) {
/* FIXME: The path to the status icon of the sender
* (available, away, etc...)
replace = message;
} else if (theme_adium_match (&cur, "%time%") ||
theme_adium_match_with_format (&cur, "%time{", &format)) {
- /* FIXME: format is not exactly strftime.
- * See NSDateFormatter spec:
- * http://www.stepcase.com/blog/2008/12/02/format-string-for-the-iphone-nsdateformatter/
- */
+ const gchar *strftime_format;
+
+ strftime_format = nsdate_to_strftime (priv->data, format);
if (is_backlog) {
dup_replace = empathy_time_to_string_local (timestamp,
- format ? format : EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
+ strftime_format ? strftime_format :
+ EMPATHY_TIME_DATE_FORMAT_DISPLAY_SHORT);
} else {
dup_replace = empathy_time_to_string_local (timestamp,
- format ? format : EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
+ strftime_format ? strftime_format :
+ EMPATHY_TIME_FORMAT_DISPLAY_SHORT);
}
replace = dup_replace;
} else if (theme_adium_match (&cur, "%shortTime%")) {
{
EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
- gchar *html;
- gsize len;
-
- html = priv->data->content_html;
- len = priv->data->content_len;
-
- /* Fallback to legacy status_html */
- if (html == NULL) {
- html = priv->data->status_html;
- len = priv->data->status_len;
- }
- if (html != NULL) {
- theme_adium_append_html (theme, "appendMessage",
- html, len, escaped, NULL, NULL, NULL,
- NULL, "event",
- empathy_time_get_current (), FALSE);
- } else {
- DEBUG ("Couldn't find HTML file for this event");
- }
+ theme_adium_append_html (theme, "appendMessage",
+ priv->data->status_html, escaped, NULL, NULL, NULL,
+ NULL, "event",
+ empathy_time_get_current (), FALSE, FALSE);
/* There is no last contact */
if (priv->last_contact) {
}
static void
-theme_adium_remove_focus_marks (EmpathyThemeAdium *theme)
+theme_adium_remove_focus_marks (EmpathyThemeAdium *theme,
+ WebKitDOMNodeList *nodes)
{
- WebKitDOMDocument *dom;
- WebKitDOMNodeList *nodes;
guint i;
- GError *error = NULL;
-
- dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
- if (dom == NULL) {
- return;
- }
-
- /* Get all nodes with focus class */
- nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
- if (nodes == NULL) {
- DEBUG ("Error getting focus nodes: %s",
- error ? error->message : "No error");
- g_clear_error (&error);
- return;
- }
/* Remove focus and firstFocus class */
for (i = 0; i < webkit_dom_node_list_get_length (nodes); i++) {
}
}
+static void
+theme_adium_remove_all_focus_marks (EmpathyThemeAdium *theme)
+{
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
+ WebKitDOMDocument *dom;
+ WebKitDOMNodeList *nodes;
+ GError *error = NULL;
+
+ if (!priv->has_unread_message)
+ return;
+
+ priv->has_unread_message = FALSE;
+
+ dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (theme));
+ if (dom == NULL) {
+ return;
+ }
+
+ /* Get all nodes with focus class */
+ nodes = webkit_dom_document_query_selector_all (dom, ".focus", &error);
+ if (nodes == NULL) {
+ DEBUG ("Error getting focus nodes: %s",
+ error ? error->message : "No error");
+ g_clear_error (&error);
+ return;
+ }
+
+ theme_adium_remove_focus_marks (theme, nodes);
+}
+
static void
theme_adium_append_message (EmpathyChatView *view,
- EmpathyMessage *msg)
+ EmpathyMessage *msg,
+ gboolean should_highlight)
{
EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (view);
EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
EmpathyContact *sender;
+ TpMessage *tp_msg;
TpAccount *account;
- gchar *body_escaped;
- const gchar *body;
+ gchar *body_escaped, *name_escaped;
const gchar *name;
const gchar *contact_id;
EmpathyAvatar *avatar;
const gchar *avatar_filename = NULL;
gint64 timestamp;
- gchar *html = NULL;
- gsize len = 0;
+ const gchar *html = NULL;
const gchar *func;
const gchar *service_name;
GString *message_classes = NULL;
gboolean is_backlog;
gboolean consecutive;
+ gboolean action;
if (priv->pages_loading != 0) {
- priv->message_queue = g_list_prepend (priv->message_queue,
- g_object_ref (msg));
+ queue_item (&priv->message_queue, QUEUED_MESSAGE, msg, NULL, should_highlight);
return;
}
if (service_name == NULL)
service_name = tp_account_get_protocol (account);
timestamp = empathy_message_get_timestamp (msg);
- body = empathy_message_get_body (msg);
- body_escaped = theme_adium_parse_body (theme, body);
- name = empathy_contact_get_alias (sender);
+ body_escaped = theme_adium_parse_body (theme,
+ empathy_message_get_body (msg),
+ empathy_message_get_token (msg));
+ 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);
- /* If this is a /me, append an event */
- if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) {
- gchar *str;
+ name_escaped = g_markup_escape_text (name, -1);
- str = g_strdup_printf ("%s %s", name, body_escaped);
- theme_adium_append_event_escaped (view, str);
+ /* If this is a /me probably */
+ if (action) {
+ gchar *str;
- g_free (str);
+ if (priv->data->version >= 4 || !priv->data->custom_template) {
+ str = g_strdup_printf ("<span class='actionMessageUserName'>%s</span>"
+ "<span class='actionMessageBody'>%s</span>",
+ name_escaped, body_escaped);
+ } else {
+ str = g_strdup_printf ("*%s*", body_escaped);
+ }
g_free (body_escaped);
- return;
+ body_escaped = str;
}
/* Get the avatar filename, or a fallback */
message_classes = g_string_new ("message");
if (!priv->has_focus && !is_backlog) {
if (!priv->has_unread_message) {
- /* This is the first message we receive since we lost
- * focus; remove previous unread marks. */
- theme_adium_remove_focus_marks (theme);
-
g_string_append (message_classes, " firstFocus");
priv->has_unread_message = TRUE;
}
} else {
g_string_append (message_classes, " incoming");
}
+ if (should_highlight) {
+ g_string_append (message_classes, " mention");
+ }
+ if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_AUTO_REPLY) {
+ g_string_append (message_classes, " autoreply");
+ }
+ if (action) {
+ g_string_append (message_classes, " action");
+ }
/* FIXME: other classes:
- * autoreply - the message is an automatic response, generally due to an
- * away status
- * mention - the incoming message (in groupchat) matches your username
- * or one of the mention keywords specified in Adium's
- * advanced prefs.
* status - the message is a status change
* event - the message is a notification of something happening
* (for example, encryption being turned on)
- * %status% - See %status% in theme_adium_append_html()
+ * %status% - See %status% in theme_adium_append_html ()
*/
+ /* This is slightly a hack, but it's the only way to add
+ * arbitrary data to messages in the HTML. We add another
+ * class called "x-empathy-message-id-*" to the message. This
+ * way, we can remove the unread marker for this specific
+ * message later. */
+ tp_msg = empathy_message_get_tp_message (msg);
+ if (tp_msg != NULL) {
+ 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 */
if (consecutive) {
- func = "appendNextMessage";
+ func = priv->allow_scrolling ? "appendNextMessage" : "appendNextMessageNoScroll";
} else {
- func = "appendMessage";
- }
-
- html = priv->data->content_html;
- len = priv->data->content_len;
-
- /* Fallback to legacy Outgoing */
- if (html == NULL && empathy_contact_is_user (sender)) {
- if (consecutive) {
- if (is_backlog) {
- html = priv->data->out_nextcontext_html;
- len = priv->data->out_nextcontext_len;
- }
-
- /* Not backlog, or fallback if NextContext.html
- * is missing */
- if (html == NULL) {
- html = priv->data->out_nextcontent_html;
- len = priv->data->out_nextcontent_len;
- }
- }
-
- /* Not consecutive, or fallback if NextContext.html and/or
- * NextContent.html are missing */
- if (html == NULL) {
- if (is_backlog) {
- html = priv->data->out_context_html;
- len = priv->data->out_context_len;
- }
-
- if (html == NULL) {
- html = priv->data->out_content_html;
- len = priv->data->out_content_len;
- }
- }
+ func = priv->allow_scrolling ? "appendMessage" : "appendMessageNoScroll";
}
- /* Incoming, or fallback if outgoing files are missing */
- if (html == NULL) {
- if (consecutive) {
- if (is_backlog) {
- html = priv->data->in_nextcontext_html;
- len = priv->data->in_nextcontext_len;
- }
-
- /* Note backlog, or fallback if NextContext.html
- * is missing */
- if (html == NULL) {
- html = priv->data->in_nextcontent_html;
- len = priv->data->in_nextcontent_len;
- }
+ if (empathy_contact_is_user (sender)) {
+ /* out */
+ if (is_backlog) {
+ /* context */
+ html = consecutive ? priv->data->out_nextcontext_html : priv->data->out_context_html;
+ } else {
+ /* content */
+ html = consecutive ? priv->data->out_nextcontent_html : priv->data->out_content_html;
}
- /* Not consecutive, or fallback if NextContext.html and/or
- * NextContent.html are missing */
- if (html == NULL) {
- if (is_backlog) {
- html = priv->data->in_context_html;
- len = priv->data->in_context_len;
- }
-
- if (html == NULL) {
- html = priv->data->in_content_html;
- len = priv->data->in_content_len;
- }
+ /* remove all the unread marks when we are sending a message */
+ theme_adium_remove_all_focus_marks (theme);
+ } else {
+ /* in */
+ if (is_backlog) {
+ /* context */
+ html = consecutive ? priv->data->in_nextcontext_html : priv->data->in_context_html;
+ } else {
+ /* content */
+ html = consecutive ? priv->data->in_nextcontent_html : priv->data->in_content_html;
}
}
- if (html != NULL) {
- theme_adium_append_html (theme, func, html, len, body_escaped,
- avatar_filename, name, contact_id,
- service_name, message_classes->str,
- timestamp, is_backlog);
- } else {
- DEBUG ("Couldn't find HTML file for this message");
- }
+ theme_adium_append_html (theme, func, html, body_escaped,
+ avatar_filename, name_escaped, contact_id,
+ service_name, message_classes->str,
+ timestamp, is_backlog, empathy_contact_is_user (sender));
/* Keep the sender of the last displayed message */
if (priv->last_contact) {
priv->last_is_backlog = is_backlog;
g_free (body_escaped);
+ g_free (name_escaped);
g_string_free (message_classes, TRUE);
}
theme_adium_append_event (EmpathyChatView *view,
const gchar *str)
{
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
gchar *str_escaped;
+ if (priv->pages_loading != 0) {
+ queue_item (&priv->message_queue, QUEUED_EVENT, NULL, str, FALSE);
+ return;
+ }
+
str_escaped = g_markup_escape_text (str, -1);
theme_adium_append_event_escaped (view, str_escaped);
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, FALSE);
+ 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)
{
- /* FIXME: Is it possible? I guess we need a js function, but I don't
- * see any... */
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
+
+ priv->allow_scrolling = allow_scrolling;
+ if (allow_scrolling) {
+ empathy_chat_view_scroll_down (view);
+ }
}
static void
theme_adium_scroll_down (EmpathyChatView *view)
{
- webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "scrollToBottom()");
+ webkit_web_view_execute_script (WEBKIT_WEB_VIEW (view), "alignChat(true);");
}
static gboolean
theme_adium_clear (EmpathyChatView *view)
{
EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
- gchar *basedir_uri;
- priv->pages_loading++;
- basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
- webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (view),
- priv->data->template_html,
- basedir_uri);
- g_free (basedir_uri);
+ theme_adium_load_template (EMPATHY_THEME_ADIUM (view));
/* Clear last contact to avoid trying to add a 'joined'
* message when we don't have an insertion point. */
webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (view));
}
+static void
+theme_adium_remove_mark_from_message (EmpathyThemeAdium *self,
+ guint32 id)
+{
+ WebKitDOMDocument *dom;
+ WebKitDOMNodeList *nodes;
+ gchar *class;
+ GError *error = NULL;
+
+ dom = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (self));
+ if (dom == NULL) {
+ return;
+ }
+
+ 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);
+ g_free (class);
+
+ if (nodes == NULL) {
+ DEBUG ("Error getting focus nodes: %s",
+ error ? error->message : "No error");
+ g_clear_error (&error);
+ return;
+ }
+
+ theme_adium_remove_focus_marks (self, nodes);
+}
+
+static void
+theme_adium_remove_acked_message_unread_mark_foreach (gpointer data,
+ gpointer user_data)
+{
+ EmpathyThemeAdium *self = user_data;
+ guint32 id = GPOINTER_TO_UINT (data);
+
+ theme_adium_remove_mark_from_message (self, id);
+}
+
static void
theme_adium_focus_toggled (EmpathyChatView *view,
gboolean has_focus)
EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
priv->has_focus = has_focus;
- if (priv->has_focus) {
+ if (!priv->has_focus) {
+ /* We've lost focus, so let's make sure all the acked
+ * messages have lost their unread marker. */
+ g_queue_foreach (&priv->acked_messages,
+ theme_adium_remove_acked_message_unread_mark_foreach,
+ view);
+ g_queue_clear (&priv->acked_messages);
+
priv->has_unread_message = FALSE;
}
}
static void
-theme_adium_context_menu_selection_done_cb (GtkMenuShell *menu, gpointer user_data)
+theme_adium_message_acknowledged (EmpathyChatView *view,
+ EmpathyMessage *message)
{
- WebKitHitTestResult *hit_test_result = WEBKIT_HIT_TEST_RESULT (user_data);
+ EmpathyThemeAdium *self = (EmpathyThemeAdium *) view;
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
+ TpMessage *tp_msg;
+ guint32 id;
+ gboolean valid;
- g_object_unref (hit_test_result);
-}
+ tp_msg = empathy_message_get_tp_message (message);
-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);
+ if (tp_msg == NULL) {
+ 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,
+ GUINT_TO_POINTER (id));
+ return;
+ }
+
+ 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;
iface->highlight = theme_adium_highlight;
iface->copy_clipboard = theme_adium_copy_clipboard;
iface->focus_toggled = theme_adium_focus_toggled;
+ iface->message_acknowledged = theme_adium_message_acknowledged;
}
static void
{
EmpathyThemeAdiumPriv *priv = GET_PRIV (view);
EmpathyChatView *chat_view = EMPATHY_CHAT_VIEW (view);
+ GList *l;
DEBUG ("Page loaded");
priv->pages_loading--;
return;
/* Display queued messages */
- priv->message_queue = g_list_reverse (priv->message_queue);
- while (priv->message_queue) {
- EmpathyMessage *message = priv->message_queue->data;
+ for (l = priv->message_queue.head; l != NULL; l = l->next) {
+ QueuedItem *item = l->data;
+
+ switch (item->type)
+ {
+ case QUEUED_MESSAGE:
+ theme_adium_append_message (chat_view, item->msg,
+ item->should_highlight);
+ 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;
+ }
- theme_adium_append_message (chat_view, message);
- priv->message_queue = g_list_remove (priv->message_queue, message);
- g_object_unref (message);
+ free_queued_item (item);
}
+
+ g_queue_clear (&priv->message_queue);
}
static void
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);
}
priv->inspector_window = NULL;
}
+ if (priv->acked_messages.length > 0) {
+ g_queue_clear (&priv->acked_messages);
+ }
+
G_OBJECT_CLASS (empathy_theme_adium_parent_class)->dispose (object);
}
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)
{
EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
- gchar *basedir_uri;
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 */
object);
/* Load template */
- priv->pages_loading = 1;
+ theme_adium_load_template (EMPATHY_THEME_ADIUM (object));
- basedir_uri = g_strconcat ("file://", priv->data->basedir, NULL);
- webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (object),
- priv->data->template_html,
- basedir_uri);
- g_free (basedir_uri);
+ priv->in_construction = FALSE;
}
static void
case PROP_ADIUM_DATA:
g_value_set_boxed (value, priv->data);
break;
+ case PROP_VARIANT:
+ g_value_set_string (value, priv->variant);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break;
const GValue *value,
GParamSpec *pspec)
{
+ EmpathyThemeAdium *theme = EMPATHY_THEME_ADIUM (object);
EmpathyThemeAdiumPriv *priv = GET_PRIV (object);
switch (param_id) {
g_assert (priv->data == NULL);
priv->data = g_value_dup_boxed (value);
break;
+ case PROP_VARIANT:
+ empathy_theme_adium_set_variant (theme, g_value_get_string (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break;
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class,
+ PROP_VARIANT,
+ g_param_spec_string ("variant",
+ "The theme variant",
+ "Variant name for the theme",
+ NULL,
+ G_PARAM_CONSTRUCT |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
g_type_class_add_private (object_class, sizeof (EmpathyThemeAdiumPriv));
}
theme->priv = priv;
+ priv->in_construction = TRUE;
+ g_queue_init (&priv->message_queue);
+ priv->allow_scrolling = TRUE;
priv->smiley_manager = empathy_smiley_manager_dup_singleton ();
g_signal_connect (theme, "load-finished",
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),
}
EmpathyThemeAdium *
-empathy_theme_adium_new (EmpathyAdiumData *data)
+empathy_theme_adium_new (EmpathyAdiumData *data,
+ const gchar *variant)
{
g_return_val_if_fail (data != NULL, NULL);
return g_object_new (EMPATHY_TYPE_THEME_ADIUM,
"adium-data", data,
+ "variant", variant,
NULL);
}
+void
+empathy_theme_adium_set_variant (EmpathyThemeAdium *theme,
+ const gchar *variant)
+{
+ EmpathyThemeAdiumPriv *priv = GET_PRIV (theme);
+ gchar *variant_path;
+ gchar *script;
+
+ if (!tp_strdiff (priv->variant, variant)) {
+ return;
+ }
+
+ g_free (priv->variant);
+ priv->variant = g_strdup (variant);
+
+ if (priv->in_construction) {
+ return;
+ }
+
+ DEBUG ("Update view with variant: '%s'", variant);
+ variant_path = adium_info_dup_path_for_variant (priv->data->info,
+ priv->variant);
+ script = g_strdup_printf ("setStylesheet(\"mainStyle\",\"%s\");", variant_path);
+
+ webkit_web_view_execute_script (WEBKIT_WEB_VIEW (theme), script);
+
+ g_free (variant_path);
+ g_free (script);
+
+ 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)
{
ret = g_file_test (file, G_FILE_TEST_EXISTS);
g_free (file);
- if (ret == FALSE)
- return ret;
+ if (!ret)
+ return FALSE;
/* We ship a default Template.html as fallback if there is any problem
* with the one inside the theme. The only other required file is
- * Content.html */
+ * Content.html OR Incoming/Content.html*/
file = g_build_filename (path, "Contents", "Resources", "Content.html",
NULL);
ret = g_file_test (file, G_FILE_TEST_EXISTS);
g_free (file);
- if (ret)
- return ret;
-
- /* Legacy themes have Incoming/Content.html (outgoing fallback to use
- * incoming). */
- file = g_build_filename (path, "Contents", "Resources", "Incoming",
- "Content.html", NULL);
- ret = g_file_test (file, G_FILE_TEST_EXISTS);
- g_free (file);
+ if (!ret) {
+ file = g_build_filename (path, "Contents", "Resources", "Incoming",
+ "Content.html", NULL);
+ ret = g_file_test (file, G_FILE_TEST_EXISTS);
+ g_free (file);
+ }
return ret;
}
return info;
}
+static guint
+adium_info_get_version (GHashTable *info)
+{
+ return tp_asv_get_int32 (info, "MessageViewVersion", NULL);
+}
+
+static const gchar *
+adium_info_get_no_variant_name (GHashTable *info)
+{
+ const gchar *name = tp_asv_get_string (info, "DisplayNameForNoVariant");
+ return name ? name : _("Normal");
+}
+
+static gchar *
+adium_info_dup_path_for_variant (GHashTable *info,
+ const gchar *variant)
+{
+ guint version = adium_info_get_version (info);
+ const gchar *no_variant = adium_info_get_no_variant_name (info);
+ GPtrArray *variants;
+ guint i;
+
+ if (version <= 2 && !tp_strdiff (variant, no_variant)) {
+ return g_strdup ("main.css");
+ }
+
+ 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;
+ }
+ }
+ if (i == variants->len) {
+ DEBUG ("Variant %s does not exist", variant);
+ variant = g_ptr_array_index (variants, 0);
+ }
+
+ return g_strdup_printf ("Variants/%s.css", variant);
+
+}
+
+const gchar *
+empathy_adium_info_get_default_variant (GHashTable *info)
+{
+ if (adium_info_get_version (info) <= 2) {
+ return adium_info_get_no_variant_name (info);
+ }
+
+ return tp_asv_get_string (info, "DefaultVariant");
+}
+
+GPtrArray *
+empathy_adium_info_get_available_variants (GHashTable *info)
+{
+ GPtrArray *variants;
+ const gchar *path;
+ gchar *dirpath;
+ GDir *dir;
+
+ variants = tp_asv_get_boxed (info, "AvailableVariants", G_TYPE_PTR_ARRAY);
+ if (variants != NULL) {
+ return variants;
+ }
+
+ variants = g_ptr_array_new_with_free_func (g_free);
+ tp_asv_take_boxed (info, g_strdup ("AvailableVariants"),
+ G_TYPE_PTR_ARRAY, variants);
+
+ path = tp_asv_get_string (info, "path");
+ dirpath = g_build_filename (path, "Contents", "Resources", "Variants", NULL);
+ dir = g_dir_open (dirpath, 0, NULL);
+ if (dir != NULL) {
+ const gchar *name;
+
+ for (name = g_dir_read_name (dir);
+ name != NULL;
+ name = g_dir_read_name (dir)) {
+ gchar *display_name;
+
+ if (!g_str_has_suffix (name, ".css")) {
+ continue;
+ }
+
+ display_name = g_strdup (name);
+ strstr (display_name, ".css")[0] = '\0';
+ g_ptr_array_add (variants, display_name);
+ }
+ g_dir_close (dir);
+ }
+ g_free (dirpath);
+
+ if (adium_info_get_version (info) <= 2) {
+ g_ptr_array_add (variants,
+ g_strdup (adium_info_get_no_variant_name (info)));
+ }
+
+ return variants;
+}
+
GType
empathy_adium_data_get_type (void)
{
empathy_adium_data_new_with_info (const gchar *path, GHashTable *info)
{
EmpathyAdiumData *data;
- gchar *file;
gchar *template_html = NULL;
- gsize template_len;
gchar *footer_html = NULL;
- gsize footer_len;
- GString *string;
- gchar **strv = NULL;
- gchar *css_path;
- guint len = 0;
- guint i = 0;
+ gchar *tmp;
g_return_val_if_fail (empathy_adium_path_is_valid (path), NULL);
data->basedir = g_strconcat (path, G_DIR_SEPARATOR_S "Contents"
G_DIR_SEPARATOR_S "Resources" G_DIR_SEPARATOR_S, NULL);
data->info = g_hash_table_ref (info);
+ data->version = adium_info_get_version (info);
+ data->strings_to_free = g_ptr_array_new_with_free_func (g_free);
+ data->date_format_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal, g_free, g_free);
DEBUG ("Loading theme at %s", path);
+#define LOAD(path, var) \
+ tmp = g_build_filename (data->basedir, path, NULL); \
+ g_file_get_contents (tmp, &var, NULL, NULL); \
+ g_free (tmp); \
+
+#define LOAD_CONST(path, var) \
+ { \
+ gchar *content; \
+ LOAD (path, content); \
+ if (content != NULL) { \
+ g_ptr_array_add (data->strings_to_free, content); \
+ } \
+ var = content; \
+ }
+
/* Load html files */
- file = g_build_filename (data->basedir, "Content.html", NULL);
- g_file_get_contents (file, &data->content_html, &data->content_len, NULL);
- g_free (file);
+ LOAD_CONST ("Content.html", data->content_html);
+ LOAD_CONST ("Incoming/Content.html", data->in_content_html);
+ LOAD_CONST ("Incoming/NextContent.html", data->in_nextcontent_html);
+ LOAD_CONST ("Incoming/Context.html", data->in_context_html);
+ LOAD_CONST ("Incoming/NextContext.html", data->in_nextcontext_html);
+ LOAD_CONST ("Outgoing/Content.html", data->out_content_html);
+ LOAD_CONST ("Outgoing/NextContent.html", data->out_nextcontent_html);
+ LOAD_CONST ("Outgoing/Context.html", data->out_context_html);
+ LOAD_CONST ("Outgoing/NextContext.html", data->out_nextcontext_html);
+ LOAD_CONST ("Status.html", data->status_html);
+ LOAD ("Template.html", template_html);
+ LOAD ("Footer.html", footer_html);
+
+#undef LOAD_CONST
+#undef LOAD
+
+ /* HTML fallbacks: If we have at least content OR in_content, then
+ * everything else gets a fallback */
+
+#define FALLBACK(html, fallback) \
+ if (html == NULL) { \
+ html = fallback; \
+ }
- /* Fallback to legacy html files */
- if (data->content_html == NULL) {
- DEBUG (" fallback to legacy theme");
+ /* in_nextcontent -> in_content -> content */
+ FALLBACK (data->in_content_html, data->content_html);
+ FALLBACK (data->in_nextcontent_html, data->in_content_html);
- file = g_build_filename (data->basedir, "Incoming", "Content.html", NULL);
- g_file_get_contents (file, &data->in_content_html, &data->in_content_len, NULL);
- g_free (file);
+ /* context -> content */
+ FALLBACK (data->in_context_html, data->in_content_html);
+ FALLBACK (data->in_nextcontext_html, data->in_nextcontent_html);
+ FALLBACK (data->out_context_html, data->out_content_html);
+ FALLBACK (data->out_nextcontext_html, data->out_nextcontent_html);
- file = g_build_filename (data->basedir, "Incoming", "NextContent.html", NULL);
- g_file_get_contents (file, &data->in_nextcontent_html, &data->in_nextcontent_len, NULL);
- g_free (file);
+ /* out -> in */
+ FALLBACK (data->out_content_html, data->in_content_html);
+ FALLBACK (data->out_nextcontent_html, data->in_nextcontent_html);
+ FALLBACK (data->out_context_html, data->in_context_html);
+ FALLBACK (data->out_nextcontext_html, data->in_nextcontext_html);
- file = g_build_filename (data->basedir, "Incoming", "Context.html", NULL);
- g_file_get_contents (file, &data->in_context_html, &data->in_context_len, NULL);
- g_free (file);
+ /* status -> in_content */
+ FALLBACK (data->status_html, data->in_content_html);
- file = g_build_filename (data->basedir, "Incoming", "NextContext.html", NULL);
- g_file_get_contents (file, &data->in_nextcontext_html, &data->in_nextcontext_len, NULL);
- g_free (file);
+#undef FALLBACK
- file = g_build_filename (data->basedir, "Outgoing", "Content.html", NULL);
- g_file_get_contents (file, &data->out_content_html, &data->out_content_len, NULL);
- g_free (file);
-
- file = g_build_filename (data->basedir, "Outgoing", "NextContent.html", NULL);
- g_file_get_contents (file, &data->out_nextcontent_html, &data->out_nextcontent_len, NULL);
- g_free (file);
+ /* template -> empathy's template */
+ data->custom_template = (template_html != NULL);
+ if (template_html == NULL) {
+ GError *error = NULL;
- file = g_build_filename (data->basedir, "Outgoing", "Context.html", NULL);
- g_file_get_contents (file, &data->out_context_html, &data->out_context_len, NULL);
- g_free (file);
+ tmp = empathy_file_lookup ("Template.html", "data");
- file = g_build_filename (data->basedir, "Outgoing", "NextContext.html", NULL);
- g_file_get_contents (file, &data->out_nextcontext_html, &data->out_nextcontext_len, NULL);
- g_free (file);
+ if (!g_file_get_contents (tmp, &template_html, NULL, &error)) {
+ g_warning ("couldn't load Empathy's default theme "
+ "template: %s", error->message);
+ g_return_val_if_reached (data);
+ }
- file = g_build_filename (data->basedir, "Status.html", NULL);
- g_file_get_contents (file, &data->status_html, &data->status_len, NULL);
- g_free (file);
+ g_free (tmp);
}
- file = g_build_filename (data->basedir, "Footer.html", NULL);
- g_file_get_contents (file, &footer_html, &footer_len, NULL);
- g_free (file);
-
- file = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
- if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
- data->default_incoming_avatar_filename = file;
+ /* Default avatar */
+ tmp = g_build_filename (data->basedir, "Incoming", "buddy_icon.png", NULL);
+ if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ data->default_incoming_avatar_filename = tmp;
} else {
- g_free (file);
+ g_free (tmp);
}
-
- file = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
- if (g_file_test (file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
- data->default_outgoing_avatar_filename = file;
+ tmp = g_build_filename (data->basedir, "Outgoing", "buddy_icon.png", NULL);
+ if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+ data->default_outgoing_avatar_filename = tmp;
} else {
- g_free (file);
+ g_free (tmp);
}
- css_path = g_build_filename (data->basedir, "main.css", NULL);
-
- /* There is 2 formats for Template.html: The old one has 4 parameters,
- * the new one has 5 parameters. */
- file = g_build_filename (data->basedir, "Template.html", NULL);
- if (g_file_get_contents (file, &template_html, &template_len, NULL)) {
- strv = g_strsplit (template_html, "%@", -1);
- len = g_strv_length (strv);
- }
- g_free (file);
-
- if (len != 5 && len != 6) {
- /* Either the theme has no template or it don't have the good
- * number of parameters. Fallback to use our own template. */
- g_free (template_html);
- g_strfreev (strv);
-
- file = empathy_file_lookup ("Template.html", "data");
- g_file_get_contents (file, &template_html, &template_len, NULL);
- g_free (file);
- strv = g_strsplit (template_html, "%@", -1);
- len = g_strv_length (strv);
- }
-
- /* Replace %@ with the needed information in the template html. */
- string = g_string_sized_new (template_len);
- g_string_append (string, strv[i++]);
- g_string_append (string, data->basedir);
- g_string_append (string, strv[i++]);
- if (len == 6) {
- const gchar *variant;
-
- /* We include main.css by default */
- g_string_append_printf (string, "@import url(\"%s\");", css_path);
- g_string_append (string, strv[i++]);
- variant = tp_asv_get_string (data->info, "DefaultVariant");
- if (variant) {
- g_string_append (string, "Variants/");
- g_string_append (string, variant);
- g_string_append (string, ".css");
- }
+ /* Old custom templates had only 4 parameters.
+ * New templates have 5 parameters */
+ if (data->version <= 2 && data->custom_template) {
+ tmp = string_with_format (template_html,
+ data->basedir,
+ "%@", /* Leave variant unset */
+ "", /* The header */
+ footer_html ? footer_html : "",
+ NULL);
} else {
- /* FIXME: We should set main.css OR the variant css */
- g_string_append (string, css_path);
- }
- g_string_append (string, strv[i++]);
- g_string_append (string, ""); /* We don't want header */
- g_string_append (string, strv[i++]);
- /* FIXME: We should replace adium %macros% in footer */
- if (footer_html) {
- g_string_append (string, footer_html);
+ tmp = string_with_format (template_html,
+ data->basedir,
+ data->version <= 2 ? "" : "@import url( \"main.css\" );",
+ "%@", /* Leave variant unset */
+ "", /* The header */
+ footer_html ? footer_html : "",
+ NULL);
}
- g_string_append (string, strv[i++]);
- data->template_html = g_string_free (string, FALSE);
+ g_ptr_array_add (data->strings_to_free, tmp);
+ data->template_html = tmp;
- g_free (footer_html);
g_free (template_html);
- g_free (css_path);
- g_strfreev (strv);
+ g_free (footer_html);
return data;
}
g_free (data->default_avatar_filename);
g_free (data->default_incoming_avatar_filename);
g_free (data->default_outgoing_avatar_filename);
- g_free (data->template_html);
- g_free (data->content_html);
g_hash_table_unref (data->info);
-
- g_free (data->in_content_html);
- g_free (data->in_nextcontent_html);
- g_free (data->in_context_html);
- g_free (data->in_nextcontext_html);
- g_free (data->out_content_html);
- g_free (data->out_nextcontent_html);
- g_free (data->out_context_html);
- g_free (data->out_nextcontext_html);
- g_free (data->status_html);
+ g_ptr_array_unref (data->strings_to_free);
+ tp_clear_pointer (&data->date_format_cache, g_hash_table_unref);
g_slice_free (EmpathyAdiumData, data);
}