Mon, 20 Aug 2012 20:55:36 +0200
Gadu-Gadu: add GSoC copyright note
/** * @file gtkdebug.c GTK+ Debug API * @ingroup pidgin */ /* pidgin * * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "pidgin.h" #include "notify.h" #include "prefs.h" #include "request.h" #include "util.h" #include "gtkdebug.h" #include "gtkdialogs.h" #include "gtkutils.h" #include "gtkwebview.h" #include "pidginstock.h" #include <gdk/gdkkeysyms.h> #include "gtk3compat.h" typedef struct { GtkWidget *window; GtkWidget *text; GtkWidget *filter; GtkWidget *expression; GtkWidget *filterlevel; gboolean paused; gboolean invert; gboolean highlight; guint timer; 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></head><body class=l%d></body></html>" static DebugWindow *debug_win = NULL; static guint debug_enabled_timer = 0; static gint debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused) { purple_prefs_disconnect_by_handle(pidgin_debug_get_handle()); if(debug_win->timer != 0) { const gchar *text; purple_timeout_remove(debug_win->timer); text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression)); purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text); } g_regex_unref(debug_win->regex); /* If the "Save Log" dialog is open then close it */ purple_request_close_with_handle(debug_win); g_free(debug_win); debug_win = NULL; purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE); return FALSE; } static gboolean configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win) { if (gtk_widget_get_visible(w)) { purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/width", event->width); purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/height", event->height); } return FALSE; } static void save_writefile_cb(void *user_data, const char *filename) { DebugWindow *win = (DebugWindow *)user_data; FILE *fp; char *tmp; if ((fp = g_fopen(filename, "w+")) == NULL) { purple_notify_error(win, NULL, _("Unable to open file."), NULL); return; } tmp = gtk_webview_get_body_text(GTK_WEBVIEW(win->text)); fprintf(fp, "Pidgin Debug Log : %s\n", purple_date_format_full(NULL)); fprintf(fp, "%s", tmp); g_free(tmp); fclose(fp); } static void save_cb(GtkWidget *w, DebugWindow *win) { purple_request_file(win, _("Save Debug Log"), "purple-debug.log", TRUE, G_CALLBACK(save_writefile_cb), NULL, NULL, NULL, NULL, win); } 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); } static void pause_cb(GtkWidget *w, DebugWindow *win) { 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); } } } /****************************************************************************** * regex stuff *****************************************************************************/ static void regex_clear_color(GtkWidget *w) { 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; classes = webkit_dom_html_element_get_class_list(WEBKIT_DOM_HTML_ELEMENT(div)); webkit_dom_dom_token_list_toggle(classes, "hide", NULL); } 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); } } 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); /* 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); } } 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); /* 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); } else { regex_toggle_div(div); } g_free(plaintext); g_free(text); } 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); } if (filter) { list = webkit_dom_document_get_elements_by_tag_name(dom, "div"); 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); } } } static void regex_pref_filter_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data) { DebugWindow *win = (DebugWindow *)data; gboolean active = GPOINTER_TO_INT(val), current; if (!win || !win->window) return; current = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)); if (active != current) gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), active); } static void regex_pref_expression_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data) { DebugWindow *win = (DebugWindow *)data; const gchar *exp = (const gchar *)val; gtk_entry_set_text(GTK_ENTRY(win->expression), exp); } static void regex_pref_invert_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data) { DebugWindow *win = (DebugWindow *)data; gboolean active = GPOINTER_TO_INT(val); win->invert = active; if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) regex_toggle_filter(win, TRUE); } static void regex_pref_highlight_cb(const gchar *name, PurplePrefType type, gconstpointer val, gpointer data) { DebugWindow *win = (DebugWindow *)data; gboolean active = GPOINTER_TO_INT(val); win->highlight = active; if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) regex_toggle_filter(win, TRUE); } static gboolean regex_timer_cb(DebugWindow *win) { const gchar *text; text = gtk_entry_get_text(GTK_ENTRY(win->expression)); purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text); win->timer = 0; return FALSE; } static void regex_changed_cb(GtkWidget *w, DebugWindow *win) { const gchar *text; if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), FALSE); } if (win->timer == 0) win->timer = purple_timeout_add_seconds(5, (GSourceFunc)regex_timer_cb, win); text = gtk_entry_get_text(GTK_ENTRY(win->expression)); if (text == NULL || *text == '\0') { regex_clear_color(win->expression); gtk_widget_set_sensitive(win->filter, FALSE); return; } if (win->regex) g_regex_unref(win->regex); win->regex = g_regex_new(text, G_REGEX_CASELESS, 0, NULL); if (win->regex == NULL) { /* failed to compile */ regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF); gtk_widget_set_sensitive(win->filter, FALSE); } else { /* compiled successfully */ regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF); gtk_widget_set_sensitive(win->filter, TRUE); } } static void regex_key_release_cb(GtkWidget *w, GdkEventKey *e, DebugWindow *win) { if(e->keyval == GDK_KEY_Return && gtk_widget_is_sensitive(win->filter) && !gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) { gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), TRUE); } } static void regex_menu_cb(GtkWidget *item, const gchar *pref) { gboolean active; active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)); purple_prefs_set_bool(pref, active); } static void regex_popup_cb(GtkEntry *entry, GtkWidget *menu, DebugWindow *win) { pidgin_separator(menu); pidgin_new_check_item(menu, _("Invert"), G_CALLBACK(regex_menu_cb), PIDGIN_PREFS_ROOT "/debug/invert", win->invert); pidgin_new_check_item(menu, _("Highlight matches"), G_CALLBACK(regex_menu_cb), PIDGIN_PREFS_ROOT "/debug/highlight", win->highlight); } static void regex_filter_toggled_cb(GtkToggleToolButton *button, DebugWindow *win) { gboolean active; active = gtk_toggle_tool_button_get_active(button); purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/filter", active); if (!GTK_IS_WEBVIEW(win->text)) return; regex_toggle_filter(win, active); } static void 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); g_free(tmp); } static void filter_level_changed_cb(GtkWidget *combo, gpointer null) { purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/filterlevel", gtk_combo_box_get_active(GTK_COMBO_BOX(combo))); } static void toolbar_style_pref_changed_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) { gtk_toolbar_set_style(GTK_TOOLBAR(data), GPOINTER_TO_INT(value)); } static void toolbar_icon_pref_changed(GtkWidget *item, GtkWidget *toolbar) { int style = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user_data")); purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/style", style); } static gboolean toolbar_context(GtkWidget *toolbar, GdkEventButton *event, gpointer null) { GtkWidget *menu, *item; const char *text[3]; GtkToolbarStyle value[3]; int i; if (!(event->button == 3 && event->type == GDK_BUTTON_PRESS)) return FALSE; text[0] = _("_Icon Only"); value[0] = GTK_TOOLBAR_ICONS; text[1] = _("_Text Only"); value[1] = GTK_TOOLBAR_TEXT; text[2] = _("_Both Icon & Text"); value[2] = GTK_TOOLBAR_BOTH_HORIZ; menu = gtk_menu_new(); for (i = 0; i < 3; i++) { item = gtk_check_menu_item_new_with_mnemonic(text[i]); g_object_set_data(G_OBJECT(item), "user_data", GINT_TO_POINTER(value[i])); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(toolbar_icon_pref_changed), toolbar); if (value[i] == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style")) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); } gtk_widget_show_all(menu); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time); return FALSE; } static void regex_html_appended_cb(GtkWebView *webview, WebKitDOMRange *range, DebugWindow *win) { if (!win || !win->window) return; if (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) { WebKitDOMDocument *dom; WebKitDOMHTMLElement *body; WebKitDOMNode *div; 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 DebugWindow * debug_window_new(void) { DebugWindow *win; GtkWidget *vbox; GtkWidget *toolbar; GtkWidget *frame; gint width, height; void *handle; GtkToolItem *item; #if !GTK_CHECK_VERSION(2,12,0) GtkTooltips *tooltips; #endif win = g_new0(DebugWindow, 1); width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/height"); win->window = pidgin_create_window(_("Debug Window"), 0, "debug", TRUE); purple_debug_info("gtkdebug", "Setting dimensions to %d, %d\n", width, height); gtk_window_set_default_size(GTK_WINDOW(win->window), width, height); g_signal_connect(G_OBJECT(win->window), "delete_event", G_CALLBACK(debug_window_destroy), NULL); g_signal_connect(G_OBJECT(win->window), "configure_event", G_CALLBACK(configure_cb), win); handle = pidgin_debug_get_handle(); /* Setup the vbox */ vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(win->window), vbox); if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/toolbar")) { /* Setup our top button bar thingie. */ toolbar = gtk_toolbar_new(); #if !GTK_CHECK_VERSION(2,12,0) tooltips = gtk_tooltips_new(); #endif #if !GTK_CHECK_VERSION(2,14,0) gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE); #endif gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), TRUE); g_signal_connect(G_OBJECT(toolbar), "button-press-event", G_CALLBACK(toolbar_context), win); gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style")); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/style", toolbar_style_pref_changed_cb, toolbar); gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_SMALL_TOOLBAR); gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); /* Save */ item = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE); gtk_tool_item_set_is_important(item, TRUE); gtk_tool_item_set_tooltip_text(item, _("Save")); g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(save_cb), win); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); /* Clear button */ item = gtk_tool_button_new_from_stock(GTK_STOCK_CLEAR); gtk_tool_item_set_is_important(item, TRUE); gtk_tool_item_set_tooltip_text(item, _("Clear")); g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(clear_cb), win); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); item = gtk_separator_tool_item_new(); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); /* Pause */ item = gtk_toggle_tool_button_new_from_stock(PIDGIN_STOCK_PAUSE); gtk_tool_item_set_is_important(item, TRUE); gtk_tool_item_set_tooltip_text(item, _("Pause")); g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(pause_cb), win); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); /* regex stuff */ item = gtk_separator_tool_item_new(); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); /* regex toggle button */ item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_FIND); gtk_tool_item_set_is_important(item, TRUE); win->filter = GTK_WIDGET(item); gtk_tool_button_set_label(GTK_TOOL_BUTTON(win->filter), _("Filter")); gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(win->filter), _("Filter")); g_signal_connect(G_OBJECT(win->filter), "clicked", G_CALLBACK(regex_filter_toggled_cb), win); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(win->filter)); /* we purposely disable the toggle button here in case * /purple/gtk/debug/expression has an empty string. If it does not have * an empty string, the change signal will get called and make the * toggle button sensitive. */ gtk_widget_set_sensitive(win->filter, FALSE); gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/filter")); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filter", regex_pref_filter_cb, win); /* regex entry */ win->expression = gtk_entry_new(); item = gtk_tool_item_new(); gtk_widget_set_tooltip_text(win->expression, _("Right click for more options.")); gtk_container_add(GTK_CONTAINER(item), GTK_WIDGET(win->expression)); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); /* this needs to be before the text is set from the pref if we want it * to colorize a stored expression. */ g_signal_connect(G_OBJECT(win->expression), "changed", G_CALLBACK(regex_changed_cb), win); gtk_entry_set_text(GTK_ENTRY(win->expression), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/debug/regex")); g_signal_connect(G_OBJECT(win->expression), "populate-popup", G_CALLBACK(regex_popup_cb), win); g_signal_connect(G_OBJECT(win->expression), "key-release-event", G_CALLBACK(regex_key_release_cb), win); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/regex", regex_pref_expression_cb, win); /* connect the rest of our pref callbacks */ win->invert = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/invert"); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/invert", regex_pref_invert_cb, win); win->highlight = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/highlight"); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/highlight", regex_pref_highlight_cb, win); item = gtk_separator_tool_item_new(); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); item = gtk_tool_item_new(); gtk_container_add(GTK_CONTAINER(item), gtk_label_new(_("Level "))); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); win->filterlevel = gtk_combo_box_text_new(); item = gtk_tool_item_new(); gtk_widget_set_tooltip_text(win->filterlevel, _("Select the debug filter level.")); gtk_container_add(GTK_CONTAINER(item), win->filterlevel); gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item)); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("All")); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("Misc")); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("Info")); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("Warning")); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("Error ")); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(win->filterlevel), _("Fatal Error")); gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel")); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filterlevel", filter_level_pref_changed, win); g_signal_connect(G_OBJECT(win->filterlevel), "changed", G_CALLBACK(filter_level_changed_cb), NULL); } /* Add the gtkwebview */ 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_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); gtk_widget_show(frame); g_signal_connect(G_OBJECT(win->text), "html-appended", G_CALLBACK(regex_html_appended_cb), win); clear_cb(NULL, win); gtk_widget_show_all(win->window); return win; } static gboolean debug_enabled_timeout_cb(gpointer data) { debug_enabled_timer = 0; if (data) pidgin_debug_window_show(); else pidgin_debug_window_hide(); return FALSE; } static void debug_enabled_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) { debug_enabled_timer = g_timeout_add(0, debug_enabled_timeout_cb, GINT_TO_POINTER(GPOINTER_TO_INT(value))); } static void pidgin_glib_log_handler(const gchar *domain, GLogLevelFlags flags, const gchar *msg, gpointer user_data) { PurpleDebugLevel level; char *new_msg = NULL; char *new_domain = NULL; if ((flags & G_LOG_LEVEL_ERROR) == G_LOG_LEVEL_ERROR) level = PURPLE_DEBUG_ERROR; else if ((flags & G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL) level = PURPLE_DEBUG_FATAL; else if ((flags & G_LOG_LEVEL_WARNING) == G_LOG_LEVEL_WARNING) level = PURPLE_DEBUG_WARNING; else if ((flags & G_LOG_LEVEL_MESSAGE) == G_LOG_LEVEL_MESSAGE) level = PURPLE_DEBUG_INFO; else if ((flags & G_LOG_LEVEL_INFO) == G_LOG_LEVEL_INFO) level = PURPLE_DEBUG_INFO; else if ((flags & G_LOG_LEVEL_DEBUG) == G_LOG_LEVEL_DEBUG) level = PURPLE_DEBUG_MISC; else { purple_debug_warning("gtkdebug", "Unknown glib logging level in %d\n", flags); level = PURPLE_DEBUG_MISC; /* This will never happen. */ } if (msg != NULL) new_msg = purple_utf8_try_convert(msg); if (domain != NULL) new_domain = purple_utf8_try_convert(domain); if (new_msg != NULL) { purple_debug(level, (new_domain != NULL ? new_domain : "g_log"), "%s\n", new_msg); g_free(new_msg); } g_free(new_domain); } #ifdef _WIN32 static void pidgin_glib_dummy_print_handler(const gchar *string) { } #endif void pidgin_debug_init(void) { /* Debug window preferences. */ /* * NOTE: This must be set before prefs are loaded, and the callbacks * set after they are loaded, since prefs sets the enabled * preference here and that loads the window, which calls the * configure event, which overrides the width and height! :P */ purple_prefs_add_none(PIDGIN_PREFS_ROOT "/debug"); /* Controls printing to the debug window */ purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/filterlevel", PURPLE_DEBUG_ALL); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/style", GTK_TOOLBAR_BOTH_HORIZ); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/toolbar", TRUE); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/width", 450); purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/height", 250); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/debug/regex", ""); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/filter", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/invert", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/case_insensitive", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/highlight", FALSE); purple_prefs_connect_callback(NULL, PIDGIN_PREFS_ROOT "/debug/enabled", debug_enabled_cb, NULL); #define REGISTER_G_LOG_HANDLER(name) \ g_log_set_handler((name), G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL \ | G_LOG_FLAG_RECURSION, \ pidgin_glib_log_handler, NULL) /* Register the glib/gtk log handlers. */ REGISTER_G_LOG_HANDLER(NULL); REGISTER_G_LOG_HANDLER("Gdk"); REGISTER_G_LOG_HANDLER("Gtk"); REGISTER_G_LOG_HANDLER("GdkPixbuf"); REGISTER_G_LOG_HANDLER("GLib"); REGISTER_G_LOG_HANDLER("GModule"); REGISTER_G_LOG_HANDLER("GLib-GObject"); REGISTER_G_LOG_HANDLER("GThread"); #ifdef USE_GSTREAMER REGISTER_G_LOG_HANDLER("GStreamer"); #endif #ifdef _WIN32 if (!purple_debug_is_enabled()) g_set_print_handler(pidgin_glib_dummy_print_handler); #endif } void pidgin_debug_uninit(void) { purple_debug_set_ui_ops(NULL); if (debug_enabled_timer != 0) g_source_remove(debug_enabled_timer); } void pidgin_debug_window_show(void) { if (debug_win == NULL) debug_win = debug_window_new(); gtk_widget_show(debug_win->window); purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", TRUE); } void pidgin_debug_window_hide(void) { if (debug_win != NULL) { gtk_widget_destroy(debug_win->window); debug_window_destroy(NULL, NULL, NULL); } } static void pidgin_debug_print(PurpleDebugLevel level, const char *category, const char *arg_s) { gchar *ts_s; gchar *esc_s, *cat_s, *tmp, *s; const char *mdate; time_t mtime; if (debug_win == NULL || !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled")) { return; } mtime = time(NULL); mdate = purple_utf8_strftime("%H:%M:%S", localtime(&mtime)); ts_s = g_strdup_printf("(%s) ", mdate); if (category == NULL) cat_s = g_strdup(""); else cat_s = g_strdup_printf("<b>%s:</b> ", category); tmp = purple_utf8_try_convert(arg_s); esc_s = g_markup_escape_text(tmp, -1); s = g_strdup_printf("<div class=\"l%d\">%s%s%s</div>", level, ts_s, cat_s, esc_s); g_free(ts_s); g_free(cat_s); g_free(esc_s); g_free(tmp); gtk_webview_append_html(GTK_WEBVIEW(debug_win->text), s); g_free(s); } static gboolean pidgin_debug_is_enabled(PurpleDebugLevel level, const char *category) { return (debug_win != NULL && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled")); } static PurpleDebugUiOps ops = { pidgin_debug_print, pidgin_debug_is_enabled, NULL, NULL, NULL, NULL }; PurpleDebugUiOps * pidgin_debug_get_ui_ops(void) { return &ops; } void * pidgin_debug_get_handle() { static int handle; return &handle; }