Merge from main soc.2008.masterpassword

Thu, 09 May 2013 13:15:30 +0200

author
Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
date
Thu, 09 May 2013 13:15:30 +0200
branch
soc.2008.masterpassword
changeset 34181
e46890ebd08f
parent 34180
ad7d8f434775 (current diff)
parent 33929
014800693d22 (diff)
child 34182
087c0fbac984

Merge from main

.hgignore file | annotate | diff | comparison | revisions
configure.ac file | annotate | diff | comparison | revisions
libpurple/cipher.c file | annotate | diff | comparison | revisions
pidgin/gtkprefs.c file | annotate | diff | comparison | revisions
--- a/.hgignore	Wed May 08 18:04:34 2013 +0200
+++ b/.hgignore	Thu May 09 13:15:30 2013 +0200
@@ -89,6 +89,7 @@
 pidgin.apspec$
 pidgin.desktop$
 pidgin.spec$
+pidgin/.*\.html\.h$
 pidgin/pidgin$
 pidgin/pixmaps/emotes/default/24/theme
 pidgin/pixmaps/emotes/none/theme
--- a/configure.ac	Wed May 08 18:04:34 2013 +0200
+++ b/configure.ac	Thu May 09 13:15:30 2013 +0200
@@ -102,6 +102,7 @@
 AC_SUBST(GNT_LT_VERSION_INFO)
 
 AC_PATH_PROG(sedpath, sed)
+AC_PATH_PROG(xxdpath, xxd)
 
 dnl Storing configure arguments
 AC_DEFINE_UNQUOTED(CONFIG_ARGS, "$ac_configure_args", [configure arguments])
--- a/libpurple/cipher.c	Wed May 08 18:04:34 2013 +0200
+++ b/libpurple/cipher.c	Thu May 09 13:15:30 2013 +0200
@@ -513,7 +513,7 @@
 	g_return_val_if_fail(context != NULL, -1);
 	g_return_val_if_fail(input != NULL, -1);
 	g_return_val_if_fail(output != NULL, -1);
-	g_return_val_if_fail(out_size < in_len, -1);
+	g_return_val_if_fail(out_size >= in_len, -1);
 
 	cipher = context->cipher;
 	g_return_val_if_fail(cipher, -1);
--- a/pidgin/Makefile.am	Wed May 08 18:04:34 2013 +0200
+++ b/pidgin/Makefile.am	Thu May 09 13:15:30 2013 +0200
@@ -152,6 +152,14 @@
 pidgininclude_HEADERS = \
 	$(pidgin_headers)
 
+pidgin_builtheaders = gtkdebug.html.h
+
+BUILT_SOURCES = $(pidgin_builtheaders)
+
+%.html.h: %.html
+	$(AM_V_GEN)echo "static const char $*_html[] = {" > $@
+	$(AM_V_at)$(sedpath) -e 's/^[ 	]\+//g' -e 's/[ 	]\+/ /g' $< | $(xxdpath) -i | sed -e 's/\(0x[0-9a-f][0-9a-f]\)$$/\1, 0x00/' >> $@
+	$(AM_V_at)echo "};" >> $@
 
 pidgin_DEPENDENCIES = @LIBOBJS@
 pidgin_LDFLAGS = -export-dynamic
--- a/pidgin/gtkdebug.c	Wed May 08 18:04:34 2013 +0200
+++ b/pidgin/gtkdebug.c	Thu May 09 13:15:30 2013 +0200
@@ -41,6 +41,8 @@
 
 #include "gtk3compat.h"
 
+#include "gtkdebug.html.h"
+
 typedef struct
 {
 	GtkWidget *window;
@@ -57,42 +59,6 @@
 	GRegex *regex;
 } DebugWindow;
 
-#define EMPTY_HTML \
-	"<html><head><style>" \
-	"body{white-space:pre-wrap;}" \
-	"div.l0{color:#000000;}"                    /* All debug levels. */ \
-	"div.l1{color:#666666;}"                    /* Misc.             */ \
-	"div.l2{color:#000000;}"                    /* Information.      */ \
-	"div.l3{color:#660000;}"                    /* Warnings.         */ \
-	"div.l4{color:#FF0000;}"                    /* Errors.           */ \
-	"div.l5{color:#FF0000;font-weight:bold;}"   /* Fatal errors.     */ \
-	/* Filter levels */ \
-	"div#pause~div{display:none;}" \
-	"body.l1 div.l0{display:none;}" \
-	"body.l2 div.l0,body.l2 div.l1{display:none;}" \
-	"body.l3 div.l0,body.l3 div.l1,body.l3 div.l2{display:none;}" \
-	"body.l4 div.l0,body.l4 div.l1,body.l4 div.l2,body.l4 div.l3{display:none;}" \
-	"body.l5 div.l0,body.l5 div.l1,body.l5 div.l2,body.l5 div.l3,body.l5 div.l4{display:none;}" \
-	/* Regex */ \
-	"div.hide{display:none;}" \
-	"span.regex{background-color:#ffafaf;font-weight:bold;}" \
-	"</style><script>" \
-	"function append(level, time, cat, msg) {" \
-		"var div = document.createElement('div');" \
-		"div.className = 'l' + level;" \
-		"div.appendChild(document.createTextNode('(' + time + ') '));" \
-		"if (cat) {" \
-			"var cat_n = document.createElement('b');" \
-			"cat_n.appendChild(document.createTextNode(cat + ':'));" \
-			"div.appendChild(cat_n);" \
-			"div.appendChild(document.createTextNode(' '));" \
-		"}" \
-		"div.appendChild(document.createTextNode(msg));" \
-		"document.body.appendChild(div);" \
-		"alert('appended');" \
-	"}" \
-	"</script></head><body class=l%d></body></html>"
-
 static DebugWindow *debug_win = NULL;
 static guint debug_enabled_timer = 0;
 
@@ -166,13 +132,7 @@
 static void
 clear_cb(GtkWidget *w, DebugWindow *win)
 {
-	char *tmp;
-	int level;
-
-	level = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel");
-	tmp = g_strdup_printf(EMPTY_HTML, level);
-	gtk_webview_load_html_string(GTK_WEBVIEW(win->text), tmp);
-	g_free(tmp);
+	gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), "clear();");
 }
 
 static void
@@ -180,20 +140,10 @@
 {
 	win->paused = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(w));
 
-	if (win->paused) {
-		gtk_webview_append_html(GTK_WEBVIEW(win->text), "<div id=pause></div>");
-	} else {
-		WebKitDOMDocument *dom;
-		WebKitDOMElement *pause;
-
-		dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text));
-		pause = webkit_dom_document_get_element_by_id(dom, "pause");
-		if (pause) {
-			WebKitDOMNode *parent;
-			parent = webkit_dom_node_get_parent_node(WEBKIT_DOM_NODE(pause));
-			webkit_dom_node_remove_child(parent, WEBKIT_DOM_NODE(pause), NULL);
-		}
-	}
+	if (win->paused)
+		gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), "pauseOutput();");
+	else
+		gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), "resumeOutput();");
 }
 
 /******************************************************************************
@@ -201,243 +151,63 @@
  *****************************************************************************/
 static void
 regex_clear_color(GtkWidget *w) {
+#if GTK_CHECK_VERSION(3,0,0)
+	GtkStyleContext *context = gtk_widget_get_style_context(w);
+	gtk_style_context_remove_class(context, "good-filter");
+	gtk_style_context_remove_class(context, "bad-filter");
+#else
 	gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL);
-}
-
-static void
-regex_change_color(GtkWidget *w, guint16 r, guint16 g, guint16 b) {
-	GdkColor color;
-
-	color.red = r;
-	color.green = g;
-	color.blue = b;
-
-	gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color);
-}
-
-static void
-regex_toggle_div(WebKitDOMNode *div)
-{
-	WebKitDOMDOMTokenList *classes;
-
-	if (!WEBKIT_DOM_IS_HTML_ELEMENT(div))
-		return;
-
-#if (WEBKIT_MAJOR_VERSION == 1 && \
-     WEBKIT_MINOR_VERSION == 9 && \
-     WEBKIT_MICRO_VERSION == 90)
-	/* Workaround WebKit API bug. */
-	classes = webkit_dom_element_get_class_list(WEBKIT_DOM_ELEMENT(div));
-#else
-	classes = webkit_dom_html_element_get_class_list(WEBKIT_DOM_HTML_ELEMENT(div));
 #endif
-	webkit_dom_dom_token_list_toggle(classes, "hide", NULL);
-	g_object_unref(classes);
-}
-
-static void
-regex_highlight_clear(WebKitDOMDocument *dom)
-{
-	WebKitDOMNodeList *nodes;
-	gulong i;
-
-	/* Remove highlighting SPANs */
-	nodes = webkit_dom_document_get_elements_by_class_name(dom, "regex");
-	i = webkit_dom_node_list_get_length(nodes);
-	while (i--) {
-		WebKitDOMNode *span, *parent;
-		char *content;
-		WebKitDOMText *text;
-		GError *err = NULL;
-
-		span = webkit_dom_node_list_item(nodes, i);
-		parent = webkit_dom_node_get_parent_node(span);
-
-		content = webkit_dom_node_get_text_content(span);
-		text = webkit_dom_document_create_text_node(dom, content);
-		g_free(content);
-
-		webkit_dom_node_replace_child(parent, WEBKIT_DOM_NODE(text), span, &err);
-	}
-
-	g_object_unref(nodes);
 }
 
 static void
-regex_highlight_text_nodes(WebKitDOMDocument *dom, WebKitDOMNode *div,
-                           gint start_pos, gint end_pos)
-{
-	GSList *data = NULL;
-	WebKitDOMNode *node;
-	WebKitDOMRange *range;
-	WebKitDOMElement *span;
-	gint ind, end_ind;
-	gint this_start, this_end;
-
-	ind = 0;
-	webkit_dom_node_normalize(div);
-	node = div;
-
-	/* First, find the container nodes and offsets to apply highlighting. */
-	do {
-		if (webkit_dom_node_get_node_type(node) == 3/*TEXT_NODE*/) {
-			/* The GObject model does not correctly reflect the type, hence the
-			   regular cast. */
-			end_ind = ind + webkit_dom_character_data_get_length((WebKitDOMCharacterData*)node);
-
-			if (start_pos <= ind)
-				this_start = 0;
-			else if (start_pos < end_ind)
-				this_start = start_pos - ind;
-			else
-				this_start = -1;
-
-			if (end_pos < end_ind)
-				this_end = end_pos - ind;
-			else
-				this_end = end_ind - ind;
-
-			if (this_start != -1 && this_start < this_end) {
-				data = g_slist_prepend(data, GINT_TO_POINTER(this_end));
-				data = g_slist_prepend(data, GINT_TO_POINTER(this_start));
-				data = g_slist_prepend(data, node);
-			}
-
-			ind = end_ind;
-		}
-
-		if (webkit_dom_node_has_child_nodes(node)) {
-			node = webkit_dom_node_get_first_child(node);
-		} else {
-			while (node != div) {
-				WebKitDOMNode *next;
-
-				next = webkit_dom_node_get_next_sibling(node);
-				if (next) {
-					node = next;
-					break;
-				} else {
-					node = webkit_dom_node_get_parent_node(node);
-				}
-			}
-		}
-	} while (node != div);
+regex_change_color(GtkWidget *w, gboolean success) {
+#if GTK_CHECK_VERSION(3,0,0)
+	GtkStyleContext *context = gtk_widget_get_style_context(w);
 
-	/* Second, apply highlighting to saved sections. Changing the DOM is
-	   automatically reflected in all WebKit API, so we have to do this after
-	   finding the offsets, or things could get complicated. */
-	while (data) {
-		node = WEBKIT_DOM_NODE(data->data);
-		data = g_slist_delete_link(data, data);
-		this_start = GPOINTER_TO_INT(data->data);
-		data = g_slist_delete_link(data, data);
-		this_end = GPOINTER_TO_INT(data->data);
-		data = g_slist_delete_link(data, data);
-
-		range = webkit_dom_document_create_range(dom);
-		webkit_dom_range_set_start(range, node, this_start, NULL);
-		webkit_dom_range_set_end(range, node, this_end, NULL);
-		span = webkit_dom_document_create_element(dom, "span", NULL);
-		webkit_dom_html_element_set_class_name(WEBKIT_DOM_HTML_ELEMENT(span),
-		                                       "regex");
-		webkit_dom_range_surround_contents(range, WEBKIT_DOM_NODE(span), NULL);
+	if (success) {
+		gtk_style_context_add_class(context, "good-filter");
+		gtk_style_context_remove_class(context, "bad-filter");
+	} else {
+		gtk_style_context_add_class(context, "bad-filter");
+		gtk_style_context_remove_class(context, "good-filter");
 	}
-}
-
-static void
-regex_match(DebugWindow *win, WebKitDOMDocument *dom, WebKitDOMNode *div)
-{
-	GMatchInfo *match_info;
-	gchar *text;
-	gchar *plaintext;
-
-	text = webkit_dom_node_get_text_content(div);
-	if (!text)
-		return;
-
-	/* I don't like having to do this, but we need it for highlighting.  Plus
-	 * it makes the ^ and $ operators work :)
-	 */
-	plaintext = purple_markup_strip_html(text);
+#else
+	GdkColor color;
 
-	/* We do a first pass to see if it matches at all.  If it does we work out
-	 * the offsets to highlight.
-	 */
-	if (g_regex_match(win->regex, plaintext, 0, &match_info) != win->invert) {
-		/* If we're not highlighting or the expression is inverted, we're
-		 * done and move on.
-		 */
-		if (!win->highlight || win->invert) {
-			g_free(plaintext);
-			g_match_info_free(match_info);
-			return;
-		}
-
-		do {
-			gint m, count;
-			gint start_pos, end_pos;
-
-			if (!g_match_info_matches(match_info))
-				break;
-
-			count = g_match_info_get_match_count(match_info);
-			if (count == 1)
-				m = 0;
-			else
-				m = 1;
-
-			for (; m < count; m++)
-			{
-				g_match_info_fetch_pos(match_info, m, &start_pos, &end_pos);
-
-				if (end_pos == -1)
-					break;
-
-				regex_highlight_text_nodes(dom, div, start_pos, end_pos);
-			}
-		} while (g_match_info_next(match_info, NULL));
-
-		g_match_info_free(match_info);
+	if (success) {
+		color.red = 0xAFFF;
+		color.green = 0xFFFF;
+		color.blue = 0xAFFF;
 	} else {
-		regex_toggle_div(div);
+		color.red = 0xFFFF;
+		color.green = 0xAFFF;
+		color.blue = 0xAFFF;
 	}
 
-	g_free(plaintext);
-	g_free(text);
+	gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color);
+#endif
 }
 
 static void
 regex_toggle_filter(DebugWindow *win, gboolean filter)
 {
-	WebKitDOMDocument *dom;
-	WebKitDOMNodeList *list;
-	gulong i;
-
-	dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text));
-
-	if (win->highlight)
-		regex_highlight_clear(dom);
-
-	/* Re-show debug lines that didn't match regex */
-	list = webkit_dom_document_get_elements_by_class_name(dom, "hide");
-	i = webkit_dom_node_list_get_length(list);
-
-	while (i--) {
-		WebKitDOMNode *div = webkit_dom_node_list_item(list, i);
-		regex_toggle_div(div);
-	}
-
-	g_object_unref(list);
+	gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), "regex.clear();");
 
 	if (filter) {
-		list = webkit_dom_document_get_elements_by_tag_name(dom, "div");
+		const char *text;
+		char *regex;
+		char *script;
 
-		for (i = 0; i < webkit_dom_node_list_get_length(list); i++) {
-			WebKitDOMNode *div = webkit_dom_node_list_item(list, i);
-			regex_match(win, dom, div);
-		}
-
-		g_object_unref(list);
+		text = gtk_entry_get_text(GTK_ENTRY(win->expression));
+		regex = gtk_webview_quote_js_string(text);
+		script = g_strdup_printf("regex.filterAll(%s, %s, %s);",
+		                         regex,
+		                         win->invert ? "true" : "false",
+		                         win->highlight ? "true" : "false");
+		gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), script);
+		g_free(script);
+		g_free(regex);
 	}
 }
 
@@ -526,14 +296,18 @@
 
 	if (win->regex)
 		g_regex_unref(win->regex);
+#if GLIB_CHECK_VERSION(2,34,0)
+	win->regex = g_regex_new(text, G_REGEX_CASELESS|G_REGEX_JAVASCRIPT_COMPAT, 0, NULL);
+#else
 	win->regex = g_regex_new(text, G_REGEX_CASELESS, 0, NULL);
+#endif
 	if (win->regex == NULL) {
 		/* failed to compile */
-		regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF);
+		regex_change_color(win->expression, FALSE);
 		gtk_widget_set_sensitive(win->filter, FALSE);
 	} else {
 		/* compiled successfully */
-		regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF);
+		regex_change_color(win->expression, TRUE);
 		gtk_widget_set_sensitive(win->filter, TRUE);
 	}
 }
@@ -587,18 +361,14 @@
 filter_level_pref_changed(const char *name, PurplePrefType type, gconstpointer value, gpointer data)
 {
 	DebugWindow *win = data;
-	WebKitDOMDocument *dom;
-	WebKitDOMHTMLElement *body;
 	int level = GPOINTER_TO_INT(value);
 	char *tmp;
 
 	if (level != gtk_combo_box_get_active(GTK_COMBO_BOX(win->filterlevel)))
 		gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel), level);
 
-	dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text));
-	body = webkit_dom_document_get_body(dom);
-	tmp = g_strdup_printf("l%d", level);
-	webkit_dom_html_element_set_class_name(body, tmp);
+	tmp = g_strdup_printf("setFilterLevel('%d');", level);
+	gtk_webview_safe_execute_script(GTK_WEBVIEW(win->text), tmp);
 	g_free(tmp);
 }
 
@@ -654,42 +424,6 @@
 	return FALSE;
 }
 
-static void
-debug_window_appended(DebugWindow *win)
-{
-	WebKitDOMDocument *dom;
-	WebKitDOMHTMLElement *body;
-	WebKitDOMNode *div;
-
-	if (!gtk_toggle_tool_button_get_active(
-		GTK_TOGGLE_TOOL_BUTTON(win->filter)))
-		return;
-
-	dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(win->text));
-	body = webkit_dom_document_get_body(dom);
-	div = webkit_dom_node_get_last_child(WEBKIT_DOM_NODE(body));
-
-	if (webkit_dom_element_webkit_matches_selector(
-		WEBKIT_DOM_ELEMENT(div), "body>div:not(#pause)", NULL))
-		regex_match(win, dom, div);
-}
-
-static gboolean debug_window_alert_cb(WebKitWebView *webview,
-	WebKitWebFrame *frame, gchar *message, gpointer _win)
-{
-	DebugWindow *win = _win;
-
-	if (!win || !win->window)
-		return FALSE;
-
-	if (g_strcmp0(message, "appended") == 0) {
-		debug_window_appended(win);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
 static DebugWindow *
 debug_window_new(void)
 {
@@ -700,6 +434,23 @@
 	gint width, height;
 	void *handle;
 	GtkToolItem *item;
+#if GTK_CHECK_VERSION(3,0,0)
+	GtkStyleContext *context;
+	GtkCssProvider *filter_css;
+	const gchar filter_style[] =
+		".bad-filter {"
+			"color: @error_fg_color;"
+			"text-shadow: 0 1px @error_text_shadow;"
+			"background-image: none;"
+			"background-color: @error_bg_color;"
+		"}"
+		".good-filter {"
+			"color: @question_fg_color;"
+			"text-shadow: 0 1px @question_text_shadow;"
+			"background-image: none;"
+			"background-color: @success_color;"
+		"}";
+#endif
 
 	win = g_new0(DebugWindow, 1);
 
@@ -793,6 +544,16 @@
 		gtk_container_add(GTK_CONTAINER(item), GTK_WIDGET(win->expression));
 		gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
 
+#if GTK_CHECK_VERSION(3,0,0)
+		/* TODO: implement it for GTK2 */
+		filter_css = gtk_css_provider_new();
+		gtk_css_provider_load_from_data(filter_css, filter_style, -1, NULL);
+		context = gtk_widget_get_style_context(win->expression);
+		gtk_style_context_add_provider(context,
+		                               GTK_STYLE_PROVIDER(filter_css),
+		                               GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#endif
+
 		/* this needs to be before the text is set from the pref if we want it
 		 * to colorize a stored expression.
 		 */
@@ -848,13 +609,10 @@
 	frame = pidgin_create_webview(FALSE, &win->text, NULL, NULL);
 	gtk_webview_set_format_functions(GTK_WEBVIEW(win->text),
 	                                 GTK_WEBVIEW_ALL ^ GTK_WEBVIEW_SMILEY ^ GTK_WEBVIEW_IMAGE);
-	gtk_webview_set_autoscroll(GTK_WEBVIEW(win->text), TRUE);
+	gtk_webview_load_html_string(GTK_WEBVIEW(win->text), gtkdebug_html);
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	gtk_widget_show(frame);
 
-	g_signal_connect(G_OBJECT(win->text), "script-alert",
-		G_CALLBACK(debug_window_alert_cb), win);
-
 	clear_cb(NULL, win);
 
 	gtk_widget_show_all(win->window);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkdebug.html	Thu May 09 13:15:30 2013 +0200
@@ -0,0 +1,251 @@
+<!DOCTYPE html>
+<html>
+	<head>
+	<style>
+		body{white-space:pre-wrap;}
+		div.l0{color:#000000;}                    /* All debug levels. */
+		div.l1{color:#666666;}                    /* Misc.             */
+		div.l2{color:#000000;}                    /* Information.      */
+		div.l3{color:#660000;}                    /* Warnings.         */
+		div.l4{color:#FF0000;}                    /* Errors.           */
+		div.l5{color:#FF0000;font-weight:bold;}   /* Fatal errors.     */
+		/* Filter levels */
+		div#pause~div{display:none;}
+		body.l1 div.l0{display:none;}
+		body.l2 div.l0,body.l2 div.l1{display:none;}
+		body.l3 div.l0,body.l3 div.l1,body.l3 div.l2{display:none;}
+		body.l4 div.l0,body.l4 div.l1,body.l4 div.l2,body.l4 div.l3{display:none;}
+		body.l5 div.l0,body.l5 div.l1,body.l5 div.l2,body.l5 div.l3,body.l5 div.l4{display:none;}
+		/* Regex */
+		div.hide{display:none;}
+		span.regex{background-color:#ffafaf;font-weight:bold;}
+	</style>
+	<script>
+		function nearBottom() {
+			return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.5 ) ) );
+		}
+
+		function scrollToBottom() {
+			document.body.scrollTop = document.body.offsetHeight;
+		}
+
+		regex = {
+			clear: function () {
+				var list, i;
+				var scroll = nearBottom();
+
+				/* Remove highlighting SPANs */
+				list = document.getElementsByClassName('regex');
+				i = list.length;
+				while (i--) {
+					var span = list[i];
+					var parent = span.parentNode;
+					var content = span.textContent;
+					var text = document.createTextNode(content);
+					parent.replaceChild(text, span);
+				}
+
+				/* Remove hidden DIVs */
+				list = document.getElementsByClassName('hide');
+				i = list.length;
+				while (i--) {
+					list[i].classList.remove('hide');
+				}
+
+				if (scroll)
+					scrollToBottom();
+				this.enabled = false;
+			},
+
+			highlightTextNodes: function (div, start_pos, end_pos) {
+				var data = [], node, range, span, contents;
+				var ind, end_ind
+				var this_start, this_end;
+
+				ind = 0;
+				div.normalize();
+				node = div;
+
+				/* First, find the container nodes and offsets to apply highlighting. */
+				do {
+					if (node.nodeType === Node.TEXT_NODE) {
+						end_ind = ind + node.length;
+
+						if (start_pos <= ind)
+							this_start = 0;
+						else if (start_pos < end_ind)
+							this_start = start_pos - ind;
+						else
+							this_start = -1;
+
+						if (end_pos < end_ind)
+							this_end = end_pos - ind;
+						else
+							this_end = end_ind - ind;
+
+						if (this_start != -1 && this_start < this_end) {
+							data.push(this_end, this_start, node);
+						}
+
+						ind = end_ind;
+					}
+
+					if (node.hasChildNodes()) {
+						node = node.firstChild;
+					} else {
+						while (node != div) {
+							var next = node.nextSibling;
+							if (next) {
+								node = next;
+								break;
+							} else {
+								node = node.parentNode;
+							}
+						}
+					}
+				} while (node != div);
+
+				/* Second, apply highlighting to saved sections. Changing the DOM is
+				   automatically reflected in all WebKit API, so we have to do this after
+				   finding the offsets, or things could get complicated. */
+				while (data.length) {
+					node = data.pop();
+					this_start = data.pop();
+					this_end = data.pop();
+
+					range = document.createRange();
+					range.setStart(node, this_start);
+					range.setEnd(node, this_end);
+
+					span = document.createElement('span');
+					span.className = 'regex';
+
+					contents = range.extractContents();
+					range.insertNode(span);
+					span.appendChild(contents);
+				}
+			},
+
+			match: function (div) {
+				var text, match_info;
+				var m, count, start_pos, end_pos;
+
+				text = div.textContent;
+				if (!text)
+					return;
+
+				/* We do a first pass to see if it matches at all.  If it does we work out
+				 * the offsets to highlight.
+				 */
+				this.regex.lastIndex = 0;
+				var match_info = this.regex.exec(text);
+				if ((match_info != null) != this.invert) {
+					/* If we're not highlighting or the expression is inverted, we're
+					 * done and move on.
+					 */
+					if (!this.highlight || this.invert)
+						return;
+
+					do {
+						if (match_info === null)
+							break;
+
+						count = match_info.length;
+						if (count === 1)
+							m = 0;
+						else 
+							m = 1;
+
+						/* We do this because JS doesn't provide a sufficient means to
+						   determine the indices of matched groups. So we're just going
+						   to highlight the entire match instead. */
+						m = 0; count = 1;
+
+						for (; m < count; m++) {
+							start_pos = match_info.index;
+							end_pos = this.regex.lastIndex;
+							if (end_pos == -1)
+								break;
+
+							this.highlightTextNodes(div, start_pos, end_pos);
+						}
+
+						/* Workaround broken API for empty matches */
+						if (match_info.index == this.regex.lastIndex)
+							this.regex.lastIndex++;
+					} while (match_info = this.regex.exec(text));
+				} else {
+					div.classList.add('hide');
+				}
+			},
+
+			filterAll: function (str, inv, high) {
+				this.regex = new RegExp(str, 'gi');
+				this.invert = inv;
+				this.highlight = high;
+				this.enabled = true;
+				var list = document.getElementsByTagName('div');
+				for (var i = 0; i < list.length; i++)
+					this.match(list[i]);
+			},
+
+			highlight: false,
+			invert: false,
+			enabled: false,
+			regex: undefined
+		}
+
+		function append(level, time, cat, msg) {
+			var div = document.createElement('div');
+			div.className = 'l' + level;
+
+			div.appendChild(document.createTextNode('(' + time + ') '));
+
+			if (cat) {
+				var cat_n = document.createElement('b');
+				cat_n.appendChild(document.createTextNode(cat + ':'));
+				div.appendChild(cat_n);
+				div.appendChild(document.createTextNode(' '));
+			}
+
+			div.appendChild(document.createTextNode(msg));
+
+			if (regex.enabled)
+				regex.match(div);
+
+			var scroll = nearBottom();
+			document.body.appendChild(div);
+			if (scroll)
+				scrollToBottom();
+		}
+
+		function clear() {
+			document.body.innerHTML = '';
+		}
+
+		function pauseOutput() {
+			document.body.insertAdjacentHTML('beforeEnd', '<div id=pause></div>');
+		}
+
+		function resumeOutput() {
+			var pause = document.getElementById('pause');
+			if (pause) {
+				var parent = pause.parentNode;
+				parent.removeChild(pause);
+				scrollToBottom();
+			}
+		}
+
+		function setFilterLevel(l) {
+			var scroll = nearBottom();
+
+			document.body.className = 'l'+l;
+
+			if (scroll)
+				scrollToBottom();
+		}
+	</script>
+	</head>
+	<body class=l0></body>
+</html>
+
--- a/pidgin/gtkmain.c	Wed May 08 18:04:34 2013 +0200
+++ b/pidgin/gtkmain.c	Thu May 09 13:15:30 2013 +0200
@@ -429,6 +429,10 @@
 	char *opt_login_arg = NULL;
 	char *opt_session_arg = NULL;
 	char *search_path;
+#if GTK_CHECK_VERSION(3,0,0)
+	GtkCssProvider *provider;
+	GdkScreen *screen;
+#endif
 	GList *accounts;
 #ifdef HAVE_SIGNAL_H
 	int sig_indx;	/* for setting up signal catching */
@@ -440,6 +444,8 @@
 #ifndef DEBUG
 	char *segfault_message_tmp;
 #endif
+#endif
+#if defined(HAVE_SIGNAL_H) || GTK_CHECK_VERSION(3,0,0)
 	GError *error;
 #endif
 	int opt;
@@ -690,9 +696,11 @@
 
 	purple_debug_set_enabled(debug_enabled);
 
+#if !GTK_CHECK_VERSION(3,0,0)
 	search_path = g_build_filename(purple_user_dir(), "gtkrc-2.0", NULL);
 	gtk_rc_add_default_file(search_path);
 	g_free(search_path);
+#endif
 
 	gui_check = gtk_init_check(&argc, &argv);
 	if (!gui_check) {
@@ -709,6 +717,26 @@
 		return 1;
 	}
 
+#if GTK_CHECK_VERSION(3,0,0)
+	search_path = g_build_filename(purple_user_dir(), "gtk-3.0.css", NULL);
+
+	error = NULL;
+	provider = gtk_css_provider_new();
+	gui_check = gtk_css_provider_load_from_path(provider, search_path, &error);
+
+	if (gui_check && !error) {
+		screen = gdk_screen_get_default();
+		gtk_style_context_add_provider_for_screen(screen,
+		                                          GTK_STYLE_PROVIDER(provider),
+		                                          GTK_STYLE_PROVIDER_PRIORITY_USER);
+	} else {
+		purple_debug_error("gtk", "Unable to load custom gtk-3.0.css: %s\n",
+		                   error ? error->message : "(unknown error)");
+	}
+
+	g_free(search_path);
+#endif
+
 	g_set_application_name(PIDGIN_NAME);
 
 #ifdef _WIN32
--- a/pidgin/gtkprefs.c	Wed May 08 18:04:34 2013 +0200
+++ b/pidgin/gtkprefs.c	Thu May 09 13:15:30 2013 +0200
@@ -1948,6 +1948,26 @@
 static void
 network_ip_changed(GtkEntry *entry, gpointer data)
 {
+#if GTK_CHECK_VERSION(3,0,0)
+	const gchar *text = gtk_entry_get_text(entry);
+	GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(entry));
+
+	if (text && *text) {
+		if (purple_ip_address_is_valid(text)) {
+			purple_network_set_public_ip(text);
+			gtk_style_context_add_class(context, "good-ip");
+			gtk_style_context_remove_class(context, "bad-ip");
+		} else {
+			gtk_style_context_add_class(context, "bad-ip");
+			gtk_style_context_remove_class(context, "good-ip");
+		}
+
+	} else {
+		purple_network_set_public_ip("");
+		gtk_style_context_remove_class(context, "bad-ip");
+		gtk_style_context_remove_class(context, "good-ip");
+	}
+#else
 	const gchar *text = gtk_entry_get_text(entry);
 	GdkColor color;
 
@@ -1970,6 +1990,7 @@
 		purple_network_set_public_ip("");
 		gtk_widget_modify_base(GTK_WIDGET(entry), GTK_STATE_NORMAL, NULL);
 	}
+#endif
 }
 
 static gboolean
@@ -2092,6 +2113,23 @@
 	GtkWidget *vbox, *hbox, *entry;
 	GtkWidget *label, *auto_ip_checkbox, *ports_checkbox, *spin_button;
 	GtkSizeGroup *sg;
+#if GTK_CHECK_VERSION(3,0,0)
+	GtkStyleContext *context;
+	GtkCssProvider *ip_css;
+	const gchar ip_style[] =
+		".bad-ip {"
+			"color: @error_fg_color;"
+			"text-shadow: 0 1px @error_text_shadow;"
+			"background-image: none;"
+			"background-color: @error_bg_color;"
+		"}"
+		".good-ip {"
+			"color: @question_fg_color;"
+			"text-shadow: 0 1px @question_text_shadow;"
+			"background-image: none;"
+			"background-color: @success_color;"
+		"}";
+#endif
 
 	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
 	gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
@@ -2133,6 +2171,16 @@
 	g_signal_connect(G_OBJECT(entry), "changed",
 					 G_CALLBACK(network_ip_changed), NULL);
 
+#if GTK_CHECK_VERSION(3,0,0)
+	/* TODO: implement it for GTK2 */
+	ip_css = gtk_css_provider_new();
+	gtk_css_provider_load_from_data(ip_css, ip_style, -1, NULL);
+	context = gtk_widget_get_style_context(entry);
+	gtk_style_context_add_provider(context,
+	                               GTK_STYLE_PROVIDER(ip_css),
+	                               GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#endif
+
 	hbox = pidgin_add_widget_to_vbox(GTK_BOX(vbox), _("Public _IP:"),
 			sg, entry, TRUE, NULL);
 

mercurial