Change the oscar capabilities variable to be a guint64 everywhere instead
[pidgin-git.git] / pidgin / gtkdebug.c
blob075dfb48b31d93288d2ad4a14bf538326ea24939
1 /**
2 * @file gtkdebug.c GTK+ Debug API
3 * @ingroup pidgin
4 */
6 /* pidgin
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
27 #include "pidgin.h"
29 #include "notify.h"
30 #include "prefs.h"
31 #include "request.h"
32 #include "util.h"
34 #include "gtkdebug.h"
35 #include "gtkdialogs.h"
36 #include "gtkimhtml.h"
37 #include "gtkutils.h"
38 #include "pidginstock.h"
40 #ifdef HAVE_REGEX_H
41 # include <regex.h>
42 #endif /* HAVE_REGEX_H */
44 #include <gdk/gdkkeysyms.h>
46 typedef struct
48 GtkWidget *window;
49 GtkWidget *text;
51 GtkListStore *store;
53 gboolean paused;
55 #ifdef HAVE_REGEX_H
56 GtkWidget *filter;
57 GtkWidget *expression;
59 gboolean invert;
60 gboolean highlight;
62 guint timer;
64 regex_t regex;
65 #else
66 GtkWidget *find;
67 #endif /* HAVE_REGEX_H */
68 GtkWidget *filterlevel;
69 } DebugWindow;
71 static const char debug_fg_colors[][8] = {
72 "#000000", /**< All debug levels. */
73 "#666666", /**< Misc. */
74 "#000000", /**< Information. */
75 "#660000", /**< Warnings. */
76 "#FF0000", /**< Errors. */
77 "#FF0000", /**< Fatal errors. */
80 static DebugWindow *debug_win = NULL;
81 static guint debug_enabled_timer = 0;
83 #ifdef HAVE_REGEX_H
84 static void regex_filter_all(DebugWindow *win);
85 static void regex_show_all(DebugWindow *win);
86 #endif /* HAVE_REGEX_H */
88 static gint
89 debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused)
91 purple_prefs_disconnect_by_handle(pidgin_debug_get_handle());
93 #ifdef HAVE_REGEX_H
94 if(debug_win->timer != 0) {
95 const gchar *text;
97 purple_timeout_remove(debug_win->timer);
99 text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression));
100 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text);
103 regfree(&debug_win->regex);
104 #endif
106 /* If the "Save Log" dialog is open then close it */
107 purple_request_close_with_handle(debug_win);
109 g_free(debug_win);
110 debug_win = NULL;
112 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE);
114 return FALSE;
117 static gboolean
118 configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win)
120 if (GTK_WIDGET_VISIBLE(w)) {
121 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/width", event->width);
122 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/height", event->height);
125 return FALSE;
128 #ifndef HAVE_REGEX_H
129 struct _find {
130 DebugWindow *window;
131 GtkWidget *entry;
134 static void
135 do_find_cb(GtkWidget *widget, gint response, struct _find *f)
137 switch (response) {
138 case GTK_RESPONSE_OK:
139 gtk_imhtml_search_find(GTK_IMHTML(f->window->text),
140 gtk_entry_get_text(GTK_ENTRY(f->entry)));
141 break;
143 case GTK_RESPONSE_DELETE_EVENT:
144 case GTK_RESPONSE_CLOSE:
145 gtk_imhtml_search_clear(GTK_IMHTML(f->window->text));
146 gtk_widget_destroy(f->window->find);
147 f->window->find = NULL;
148 g_free(f);
149 break;
153 static void
154 find_cb(GtkWidget *w, DebugWindow *win)
156 GtkWidget *hbox, *img, *label;
157 struct _find *f;
159 if(win->find)
161 gtk_window_present(GTK_WINDOW(win->find));
162 return;
165 f = g_malloc(sizeof(struct _find));
166 f->window = win;
167 win->find = gtk_dialog_new_with_buttons(_("Find"),
168 GTK_WINDOW(win->window), GTK_DIALOG_DESTROY_WITH_PARENT,
169 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
170 GTK_STOCK_FIND, GTK_RESPONSE_OK, NULL);
171 gtk_dialog_set_default_response(GTK_DIALOG(win->find),
172 GTK_RESPONSE_OK);
173 g_signal_connect(G_OBJECT(win->find), "response",
174 G_CALLBACK(do_find_cb), f);
176 gtk_container_set_border_width(GTK_CONTAINER(win->find), PIDGIN_HIG_BOX_SPACE);
177 gtk_window_set_resizable(GTK_WINDOW(win->find), FALSE);
178 gtk_dialog_set_has_separator(GTK_DIALOG(win->find), FALSE);
179 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(win->find)->vbox), PIDGIN_HIG_BORDER);
180 gtk_container_set_border_width(
181 GTK_CONTAINER(GTK_DIALOG(win->find)->vbox), PIDGIN_HIG_BOX_SPACE);
183 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
184 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox),
185 hbox);
186 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
187 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
188 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
190 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
191 gtk_dialog_set_response_sensitive(GTK_DIALOG(win->find),
192 GTK_RESPONSE_OK, FALSE);
194 label = gtk_label_new(NULL);
195 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Search for:"));
196 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
198 f->entry = gtk_entry_new();
199 gtk_entry_set_activates_default(GTK_ENTRY(f->entry), TRUE);
200 gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(f->entry));
201 g_signal_connect(G_OBJECT(f->entry), "changed",
202 G_CALLBACK(pidgin_set_sensitive_if_input),
203 win->find);
204 gtk_box_pack_start(GTK_BOX(hbox), f->entry, FALSE, FALSE, 0);
206 gtk_widget_show_all(win->find);
207 gtk_widget_grab_focus(f->entry);
209 #endif /* HAVE_REGEX_H */
211 static void
212 save_writefile_cb(void *user_data, const char *filename)
214 DebugWindow *win = (DebugWindow *)user_data;
215 FILE *fp;
216 char *tmp;
218 if ((fp = g_fopen(filename, "w+")) == NULL) {
219 purple_notify_error(win, NULL, _("Unable to open file."), NULL);
220 return;
223 tmp = gtk_imhtml_get_text(GTK_IMHTML(win->text), NULL, NULL);
224 fprintf(fp, "Pidgin Debug Log : %s\n", purple_date_format_full(NULL));
225 fprintf(fp, "%s", tmp);
226 g_free(tmp);
228 fclose(fp);
231 static void
232 save_cb(GtkWidget *w, DebugWindow *win)
234 purple_request_file(win, _("Save Debug Log"), "purple-debug.log", TRUE,
235 G_CALLBACK(save_writefile_cb), NULL,
236 NULL, NULL, NULL,
237 win);
240 static void
241 clear_cb(GtkWidget *w, DebugWindow *win)
243 gtk_imhtml_clear(GTK_IMHTML(win->text));
245 #ifdef HAVE_REGEX_H
246 gtk_list_store_clear(win->store);
247 #endif /* HAVE_REGEX_H */
250 static void
251 pause_cb(GtkWidget *w, DebugWindow *win)
253 win->paused = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(w));
255 #ifdef HAVE_REGEX_H
256 if(!win->paused) {
257 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
258 regex_filter_all(win);
259 else
260 regex_show_all(win);
262 #endif /* HAVE_REGEX_H */
265 /******************************************************************************
266 * regex stuff
267 *****************************************************************************/
268 #ifdef HAVE_REGEX_H
269 static void
270 regex_clear_color(GtkWidget *w) {
271 gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL);
274 static void
275 regex_change_color(GtkWidget *w, guint16 r, guint16 g, guint16 b) {
276 GdkColor color;
278 color.red = r;
279 color.green = g;
280 color.blue = b;
282 gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color);
285 static void
286 regex_highlight_clear(DebugWindow *win) {
287 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
288 GtkTextIter s, e;
290 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &s);
291 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &e);
292 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "regex", &s, &e);
295 static void
296 regex_match(DebugWindow *win, const gchar *text) {
297 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
298 regmatch_t matches[4]; /* adjust if necessary */
299 size_t n_matches = sizeof(matches) / sizeof(matches[0]);
300 gchar *plaintext;
301 gint inverted;
303 if(!text)
304 return;
306 inverted = (win->invert) ? REG_NOMATCH : 0;
308 /* I don't like having to do this, but we need it for highlighting. Plus
309 * it makes the ^ and $ operators work :)
311 plaintext = purple_markup_strip_html(text);
313 /* we do a first pass to see if it matches at all. If it does we append
314 * it, and work out the offsets to highlight.
316 if(regexec(&win->regex, plaintext, n_matches, matches, 0) == inverted) {
317 GtkTextIter ins;
318 gchar *p = plaintext;
319 gint i, offset = 0;
321 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins,
322 gtk_text_buffer_get_insert(imhtml->text_buffer));
323 i = gtk_text_iter_get_offset(&ins);
325 gtk_imhtml_append_text(imhtml, text, 0);
327 /* If we're not highlighting or the expression is inverted, we're
328 * done and move on.
330 if(!win->highlight || inverted == REG_NOMATCH) {
331 g_free(plaintext);
332 return;
335 /* we use a do-while to highlight the first match, and then continue
336 * if necessary...
338 do {
339 size_t m;
341 for(m = 0; m < n_matches; m++) {
342 GtkTextIter ms, me;
344 if(matches[m].rm_eo == -1)
345 break;
347 i += offset;
349 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ms,
350 i + matches[m].rm_so);
351 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &me,
352 i + matches[m].rm_eo);
353 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "regex",
354 &ms, &me);
355 offset = matches[m].rm_eo;
358 p += offset;
359 } while(regexec(&win->regex, p, n_matches, matches, REG_NOTBOL) == inverted);
362 g_free(plaintext);
365 static gboolean
366 regex_filter_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
367 gpointer data)
369 DebugWindow *win = (DebugWindow *)data;
370 gchar *text;
371 PurpleDebugLevel level;
373 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
375 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
376 regex_match(win, text);
378 g_free(text);
380 return FALSE;
383 static void
384 regex_filter_all(DebugWindow *win) {
385 gtk_imhtml_clear(GTK_IMHTML(win->text));
387 if(win->highlight)
388 regex_highlight_clear(win);
390 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_filter_all_cb,
391 win);
394 static gboolean
395 regex_show_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
396 gpointer data)
398 DebugWindow *win = (DebugWindow *)data;
399 gchar *text;
400 PurpleDebugLevel level;
402 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
403 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
404 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
405 g_free(text);
407 return FALSE;
410 static void
411 regex_show_all(DebugWindow *win) {
412 gtk_imhtml_clear(GTK_IMHTML(win->text));
414 if(win->highlight)
415 regex_highlight_clear(win);
417 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_show_all_cb,
418 win);
421 static void
422 regex_compile(DebugWindow *win) {
423 const gchar *text;
425 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
427 if(text == NULL || *text == '\0') {
428 regex_clear_color(win->expression);
429 gtk_widget_set_sensitive(win->filter, FALSE);
430 return;
433 regfree(&win->regex);
435 if(regcomp(&win->regex, text, REG_EXTENDED | REG_ICASE) != 0) {
436 /* failed to compile */
437 regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF);
438 gtk_widget_set_sensitive(win->filter, FALSE);
439 } else {
440 /* compiled successfully */
441 regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF);
442 gtk_widget_set_sensitive(win->filter, TRUE);
445 /* we check if the filter is on in case it was only of the options that
446 * got changed, and not the expression.
448 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
449 regex_filter_all(win);
452 static void
453 regex_pref_filter_cb(const gchar *name, PurplePrefType type,
454 gconstpointer val, gpointer data)
456 DebugWindow *win = (DebugWindow *)data;
457 gboolean active = GPOINTER_TO_INT(val), current;
459 if(!win || !win->window)
460 return;
462 current = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter));
463 if(active != current)
464 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), active);
467 static void
468 regex_pref_expression_cb(const gchar *name, PurplePrefType type,
469 gconstpointer val, gpointer data)
471 DebugWindow *win = (DebugWindow *)data;
472 const gchar *exp = (const gchar *)val;
474 gtk_entry_set_text(GTK_ENTRY(win->expression), exp);
477 static void
478 regex_pref_invert_cb(const gchar *name, PurplePrefType type,
479 gconstpointer val, gpointer data)
481 DebugWindow *win = (DebugWindow *)data;
482 gboolean active = GPOINTER_TO_INT(val);
484 win->invert = active;
486 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
487 regex_filter_all(win);
490 static void
491 regex_pref_highlight_cb(const gchar *name, PurplePrefType type,
492 gconstpointer val, gpointer data)
494 DebugWindow *win = (DebugWindow *)data;
495 gboolean active = GPOINTER_TO_INT(val);
497 win->highlight = active;
499 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
500 regex_filter_all(win);
503 static void
504 regex_row_changed_cb(GtkTreeModel *model, GtkTreePath *path,
505 GtkTreeIter *iter, DebugWindow *win)
507 gchar *text;
508 PurpleDebugLevel level;
510 if(!win || !win->window)
511 return;
513 /* If the debug window is paused, we just return since it's in the store.
514 * We don't call regex_match because it doesn't make sense to check the
515 * string if it's paused. When we unpause we clear the imhtml and
516 * reiterate over the store to handle matches that were outputted when
517 * we were paused.
519 if(win->paused)
520 return;
522 gtk_tree_model_get(model, iter, 0, &text, 1, &level, -1);
524 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel")) {
525 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) {
526 regex_match(win, text);
527 } else {
528 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
532 g_free(text);
535 static gboolean
536 regex_timer_cb(DebugWindow *win) {
537 const gchar *text;
539 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
540 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text);
542 win->timer = 0;
544 return FALSE;
547 static void
548 regex_changed_cb(GtkWidget *w, DebugWindow *win) {
549 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) {
550 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter),
551 FALSE);
554 if(win->timer == 0)
555 win->timer = purple_timeout_add_seconds(5, (GSourceFunc)regex_timer_cb, win);
557 regex_compile(win);
560 static void
561 regex_key_release_cb(GtkWidget *w, GdkEventKey *e, DebugWindow *win) {
562 if(e->keyval == GDK_Return &&
563 GTK_WIDGET_IS_SENSITIVE(win->filter) &&
564 !gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
566 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), TRUE);
570 static void
571 regex_menu_cb(GtkWidget *item, const gchar *pref) {
572 gboolean active;
574 active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item));
576 purple_prefs_set_bool(pref, active);
579 static void
580 regex_popup_cb(GtkEntry *entry, GtkWidget *menu, DebugWindow *win) {
581 pidgin_separator(menu);
582 pidgin_new_check_item(menu, _("Invert"),
583 G_CALLBACK(regex_menu_cb),
584 PIDGIN_PREFS_ROOT "/debug/invert", win->invert);
585 pidgin_new_check_item(menu, _("Highlight matches"),
586 G_CALLBACK(regex_menu_cb),
587 PIDGIN_PREFS_ROOT "/debug/highlight", win->highlight);
590 static void
591 regex_filter_toggled_cb(GtkToggleToolButton *button, DebugWindow *win) {
592 gboolean active;
594 active = gtk_toggle_tool_button_get_active(button);
596 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/filter", active);
598 if(!GTK_IS_IMHTML(win->text))
599 return;
601 if(active)
602 regex_filter_all(win);
603 else
604 regex_show_all(win);
607 static void
608 filter_level_pref_changed(const char *name, PurplePrefType type, gconstpointer value, gpointer data)
610 DebugWindow *win = data;
612 if (GPOINTER_TO_INT(value) != gtk_combo_box_get_active(GTK_COMBO_BOX(win->filterlevel)))
613 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel), GPOINTER_TO_INT(value));
614 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
615 regex_filter_all(win);
616 else
617 regex_show_all(win);
619 #endif /* HAVE_REGEX_H */
621 static void
622 filter_level_changed_cb(GtkWidget *combo, gpointer null)
624 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/filterlevel",
625 gtk_combo_box_get_active(GTK_COMBO_BOX(combo)));
628 static void
629 toolbar_style_pref_changed_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data)
631 gtk_toolbar_set_style(GTK_TOOLBAR(data), GPOINTER_TO_INT(value));
634 static void
635 toolbar_icon_pref_changed(GtkWidget *item, GtkWidget *toolbar)
637 int style = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user_data"));
638 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/style", style);
641 static gboolean
642 toolbar_context(GtkWidget *toolbar, GdkEventButton *event, gpointer null)
644 GtkWidget *menu, *item;
645 const char *text[3];
646 GtkToolbarStyle value[3];
647 int i;
649 if (!(event->button == 3 && event->type == GDK_BUTTON_PRESS))
650 return FALSE;
652 text[0] = _("_Icon Only"); value[0] = GTK_TOOLBAR_ICONS;
653 text[1] = _("_Text Only"); value[1] = GTK_TOOLBAR_TEXT;
654 text[2] = _("_Both Icon & Text"); value[2] = GTK_TOOLBAR_BOTH_HORIZ;
656 menu = gtk_menu_new();
658 for (i = 0; i < 3; i++) {
659 item = gtk_check_menu_item_new_with_mnemonic(text[i]);
660 g_object_set_data(G_OBJECT(item), "user_data", GINT_TO_POINTER(value[i]));
661 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(toolbar_icon_pref_changed), toolbar);
662 if (value[i] == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style"))
663 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
664 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
667 gtk_widget_show_all(menu);
669 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
670 return FALSE;
673 static DebugWindow *
674 debug_window_new(void)
676 DebugWindow *win;
677 GtkWidget *vbox;
678 GtkWidget *toolbar;
679 GtkWidget *frame;
680 gint width, height;
681 void *handle;
682 GtkToolItem *item;
683 #if !GTK_CHECK_VERSION(2,12,0)
684 GtkTooltips *tooltips;
685 #endif
687 win = g_new0(DebugWindow, 1);
689 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/width");
690 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/height");
692 win->window = pidgin_create_window(_("Debug Window"), 0, "debug", TRUE);
693 purple_debug_info("gtkdebug", "Setting dimensions to %d, %d\n",
694 width, height);
696 gtk_window_set_default_size(GTK_WINDOW(win->window), width, height);
698 g_signal_connect(G_OBJECT(win->window), "delete_event",
699 G_CALLBACK(debug_window_destroy), NULL);
700 g_signal_connect(G_OBJECT(win->window), "configure_event",
701 G_CALLBACK(configure_cb), win);
703 handle = pidgin_debug_get_handle();
705 #ifdef HAVE_REGEX_H
706 /* the list store for all the messages */
707 win->store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
709 /* row-changed gets called when we do gtk_list_store_set, and row-inserted
710 * gets called with gtk_list_store_append, which is a
711 * completely empty row. So we just ignore row-inserted, and deal with row
712 * changed. -Gary
714 g_signal_connect(G_OBJECT(win->store), "row-changed",
715 G_CALLBACK(regex_row_changed_cb), win);
717 #endif /* HAVE_REGEX_H */
719 /* Setup the vbox */
720 vbox = gtk_vbox_new(FALSE, 0);
721 gtk_container_add(GTK_CONTAINER(win->window), vbox);
723 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/toolbar")) {
724 /* Setup our top button bar thingie. */
725 toolbar = gtk_toolbar_new();
726 #if !GTK_CHECK_VERSION(2,12,0)
727 tooltips = gtk_tooltips_new();
728 #endif
729 #if !GTK_CHECK_VERSION(2,14,0)
730 gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE);
731 #endif
732 gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), TRUE);
733 g_signal_connect(G_OBJECT(toolbar), "button-press-event", G_CALLBACK(toolbar_context), win);
735 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar),
736 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style"));
737 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/style",
738 toolbar_style_pref_changed_cb, toolbar);
739 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar),
740 GTK_ICON_SIZE_SMALL_TOOLBAR);
742 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
744 #ifndef HAVE_REGEX_H
745 /* Find button */
746 item = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
747 gtk_tool_item_set_is_important(item, TRUE);
748 #if GTK_CHECK_VERSION(2,12,0)
749 gtk_tool_item_set_tooltip_text(item, _("Find"));
750 #else
751 gtk_tool_item_set_tooltip(item, tooltips, _("Find"), NULL);
752 #endif
753 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(find_cb), win);
754 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
755 #endif /* HAVE_REGEX_H */
757 /* Save */
758 item = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
759 gtk_tool_item_set_is_important(item, TRUE);
760 #if GTK_CHECK_VERSION(2,12,0)
761 gtk_tool_item_set_tooltip_text(item, _("Save"));
762 #else
763 gtk_tool_item_set_tooltip(item, tooltips, _("Save"), NULL);
764 #endif
765 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(save_cb), win);
766 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
768 /* Clear button */
769 item = gtk_tool_button_new_from_stock(GTK_STOCK_CLEAR);
770 gtk_tool_item_set_is_important(item, TRUE);
771 #if GTK_CHECK_VERSION(2,12,0)
772 gtk_tool_item_set_tooltip_text(item, _("Clear"));
773 #else
774 gtk_tool_item_set_tooltip(item, tooltips, _("Clear"), NULL);
775 #endif
776 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(clear_cb), win);
777 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
779 item = gtk_separator_tool_item_new();
780 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
782 /* Pause */
783 item = gtk_toggle_tool_button_new_from_stock(PIDGIN_STOCK_PAUSE);
784 gtk_tool_item_set_is_important(item, TRUE);
785 #if GTK_CHECK_VERSION(2,12,0)
786 gtk_tool_item_set_tooltip_text(item, _("Pause"));
787 #else
788 gtk_tool_item_set_tooltip(item, tooltips, _("Pause"), NULL);
789 #endif
790 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(pause_cb), win);
791 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
793 #ifdef HAVE_REGEX_H
794 /* regex stuff */
795 item = gtk_separator_tool_item_new();
796 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
798 /* regex toggle button */
799 item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_FIND);
800 gtk_tool_item_set_is_important(item, TRUE);
801 win->filter = GTK_WIDGET(item);
802 gtk_tool_button_set_label(GTK_TOOL_BUTTON(win->filter), _("Filter"));
803 #if GTK_CHECK_VERSION(2,12,0)
804 gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(win->filter), _("Filter"));
805 #else
806 gtk_tooltips_set_tip(tooltips, win->filter, _("Filter"), NULL);
807 #endif
808 g_signal_connect(G_OBJECT(win->filter), "clicked", G_CALLBACK(regex_filter_toggled_cb), win);
809 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(win->filter));
811 /* we purposely disable the toggle button here in case
812 * /purple/gtk/debug/expression has an empty string. If it does not have
813 * an empty string, the change signal will get called and make the
814 * toggle button sensitive.
816 gtk_widget_set_sensitive(win->filter, FALSE);
817 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter),
818 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/filter"));
819 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filter",
820 regex_pref_filter_cb, win);
822 /* regex entry */
823 win->expression = gtk_entry_new();
824 item = gtk_tool_item_new();
825 #if GTK_CHECK_VERSION(2,12,0)
826 gtk_widget_set_tooltip_text(win->expression, _("Right click for more options."));
827 #else
828 gtk_tooltips_set_tip(tooltips, win->expression, _("Right click for more options."), NULL);
829 #endif
830 gtk_container_add(GTK_CONTAINER(item), GTK_WIDGET(win->expression));
831 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
833 /* this needs to be before the text is set from the pref if we want it
834 * to colorize a stored expression.
836 g_signal_connect(G_OBJECT(win->expression), "changed",
837 G_CALLBACK(regex_changed_cb), win);
838 gtk_entry_set_text(GTK_ENTRY(win->expression),
839 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/debug/regex"));
840 g_signal_connect(G_OBJECT(win->expression), "populate-popup",
841 G_CALLBACK(regex_popup_cb), win);
842 g_signal_connect(G_OBJECT(win->expression), "key-release-event",
843 G_CALLBACK(regex_key_release_cb), win);
844 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/regex",
845 regex_pref_expression_cb, win);
847 /* connect the rest of our pref callbacks */
848 win->invert = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/invert");
849 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/invert",
850 regex_pref_invert_cb, win);
852 win->highlight = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/highlight");
853 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/highlight",
854 regex_pref_highlight_cb, win);
856 #endif /* HAVE_REGEX_H */
858 item = gtk_separator_tool_item_new();
859 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
861 item = gtk_tool_item_new();
862 gtk_container_add(GTK_CONTAINER(item), gtk_label_new(_("Level ")));
863 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
865 win->filterlevel = gtk_combo_box_new_text();
866 item = gtk_tool_item_new();
867 #if GTK_CHECK_VERSION(2,12,0)
868 gtk_widget_set_tooltip_text(win->filterlevel, _("Select the debug filter level."));
869 #else
870 gtk_tooltips_set_tip(tooltips, win->filterlevel, _("Select the debug filter level."), NULL);
871 #endif
872 gtk_container_add(GTK_CONTAINER(item), win->filterlevel);
873 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
875 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("All"));
876 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Misc"));
877 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Info"));
878 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Warning"));
879 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Error "));
880 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Fatal Error"));
881 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel),
882 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"));
883 #ifdef HAVE_REGEX_H
884 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filterlevel",
885 filter_level_pref_changed, win);
886 #endif
887 g_signal_connect(G_OBJECT(win->filterlevel), "changed",
888 G_CALLBACK(filter_level_changed_cb), NULL);
891 /* Add the gtkimhtml */
892 frame = pidgin_create_imhtml(FALSE, &win->text, NULL, NULL);
893 gtk_imhtml_set_format_functions(GTK_IMHTML(win->text),
894 GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY ^ GTK_IMHTML_IMAGE);
895 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
896 gtk_widget_show(frame);
898 #ifdef HAVE_REGEX_H
899 /* add the tag for regex highlighting */
900 gtk_text_buffer_create_tag(GTK_IMHTML(win->text)->text_buffer, "regex",
901 "background", "#FFAFAF",
902 "weight", "bold",
903 NULL);
904 #endif /* HAVE_REGEX_H */
906 gtk_widget_show_all(win->window);
908 return win;
911 static gboolean
912 debug_enabled_timeout_cb(gpointer data)
914 debug_enabled_timer = 0;
916 if (data)
917 pidgin_debug_window_show();
918 else
919 pidgin_debug_window_hide();
921 return FALSE;
924 static void
925 debug_enabled_cb(const char *name, PurplePrefType type,
926 gconstpointer value, gpointer data)
928 debug_enabled_timer = g_timeout_add(0, debug_enabled_timeout_cb, GINT_TO_POINTER(GPOINTER_TO_INT(value)));
931 static void
932 pidgin_glib_log_handler(const gchar *domain, GLogLevelFlags flags,
933 const gchar *msg, gpointer user_data)
935 PurpleDebugLevel level;
936 char *new_msg = NULL;
937 char *new_domain = NULL;
939 if ((flags & G_LOG_LEVEL_ERROR) == G_LOG_LEVEL_ERROR)
940 level = PURPLE_DEBUG_ERROR;
941 else if ((flags & G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL)
942 level = PURPLE_DEBUG_FATAL;
943 else if ((flags & G_LOG_LEVEL_WARNING) == G_LOG_LEVEL_WARNING)
944 level = PURPLE_DEBUG_WARNING;
945 else if ((flags & G_LOG_LEVEL_MESSAGE) == G_LOG_LEVEL_MESSAGE)
946 level = PURPLE_DEBUG_INFO;
947 else if ((flags & G_LOG_LEVEL_INFO) == G_LOG_LEVEL_INFO)
948 level = PURPLE_DEBUG_INFO;
949 else if ((flags & G_LOG_LEVEL_DEBUG) == G_LOG_LEVEL_DEBUG)
950 level = PURPLE_DEBUG_MISC;
951 else
953 purple_debug_warning("gtkdebug",
954 "Unknown glib logging level in %d\n", flags);
956 level = PURPLE_DEBUG_MISC; /* This will never happen. */
959 if (msg != NULL)
960 new_msg = purple_utf8_try_convert(msg);
962 if (domain != NULL)
963 new_domain = purple_utf8_try_convert(domain);
965 if (new_msg != NULL)
967 purple_debug(level, (new_domain != NULL ? new_domain : "g_log"),
968 "%s\n", new_msg);
970 g_free(new_msg);
973 g_free(new_domain);
976 #ifdef _WIN32
977 static void
978 pidgin_glib_dummy_print_handler(const gchar *string)
981 #endif
983 void
984 pidgin_debug_init(void)
986 /* Debug window preferences. */
988 * NOTE: This must be set before prefs are loaded, and the callbacks
989 * set after they are loaded, since prefs sets the enabled
990 * preference here and that loads the window, which calls the
991 * configure event, which overrides the width and height! :P
994 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/debug");
996 /* Controls printing to the debug window */
997 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE);
998 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/filterlevel", PURPLE_DEBUG_ALL);
999 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/style", GTK_TOOLBAR_BOTH_HORIZ);
1001 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/toolbar", TRUE);
1002 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/width", 450);
1003 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/height", 250);
1005 #ifdef HAVE_REGEX_H
1006 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/debug/regex", "");
1007 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/filter", FALSE);
1008 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/invert", FALSE);
1009 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/case_insensitive", FALSE);
1010 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/highlight", FALSE);
1011 #endif /* HAVE_REGEX_H */
1013 purple_prefs_connect_callback(NULL, PIDGIN_PREFS_ROOT "/debug/enabled",
1014 debug_enabled_cb, NULL);
1016 #define REGISTER_G_LOG_HANDLER(name) \
1017 g_log_set_handler((name), G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL \
1018 | G_LOG_FLAG_RECURSION, \
1019 pidgin_glib_log_handler, NULL)
1021 /* Register the glib/gtk log handlers. */
1022 REGISTER_G_LOG_HANDLER(NULL);
1023 REGISTER_G_LOG_HANDLER("Gdk");
1024 REGISTER_G_LOG_HANDLER("Gtk");
1025 REGISTER_G_LOG_HANDLER("GdkPixbuf");
1026 REGISTER_G_LOG_HANDLER("GLib");
1027 REGISTER_G_LOG_HANDLER("GModule");
1028 REGISTER_G_LOG_HANDLER("GLib-GObject");
1029 REGISTER_G_LOG_HANDLER("GThread");
1030 #ifdef USE_GSTREAMER
1031 REGISTER_G_LOG_HANDLER("GStreamer");
1032 #endif
1034 #ifdef _WIN32
1035 if (!purple_debug_is_enabled())
1036 g_set_print_handler(pidgin_glib_dummy_print_handler);
1037 #endif
1040 void
1041 pidgin_debug_uninit(void)
1043 purple_debug_set_ui_ops(NULL);
1045 if (debug_enabled_timer != 0)
1046 g_source_remove(debug_enabled_timer);
1049 void
1050 pidgin_debug_window_show(void)
1052 if (debug_win == NULL)
1053 debug_win = debug_window_new();
1055 gtk_widget_show(debug_win->window);
1057 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", TRUE);
1060 void
1061 pidgin_debug_window_hide(void)
1063 if (debug_win != NULL) {
1064 gtk_widget_destroy(debug_win->window);
1065 debug_window_destroy(NULL, NULL, NULL);
1069 static void
1070 pidgin_debug_print(PurpleDebugLevel level, const char *category,
1071 const char *arg_s)
1073 #ifdef HAVE_REGEX_H
1074 GtkTreeIter iter;
1075 #endif /* HAVE_REGEX_H */
1076 gchar *ts_s;
1077 gchar *esc_s, *cat_s, *tmp, *s;
1078 const char *mdate;
1079 time_t mtime;
1081 if (debug_win == NULL ||
1082 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"))
1084 return;
1087 mtime = time(NULL);
1088 mdate = purple_utf8_strftime("%H:%M:%S", localtime(&mtime));
1089 ts_s = g_strdup_printf("(%s) ", mdate);
1090 if (category == NULL)
1091 cat_s = g_strdup("");
1092 else
1093 cat_s = g_strdup_printf("<b>%s:</b> ", category);
1095 esc_s = g_markup_escape_text(arg_s, -1);
1097 s = g_strdup_printf("<font color=\"%s\">%s%s%s</font>",
1098 debug_fg_colors[level], ts_s, cat_s, esc_s);
1100 g_free(ts_s);
1101 g_free(cat_s);
1102 g_free(esc_s);
1104 tmp = purple_utf8_try_convert(s);
1105 g_free(s);
1106 s = tmp;
1108 if (level == PURPLE_DEBUG_FATAL) {
1109 tmp = g_strdup_printf("<b>%s</b>", s);
1110 g_free(s);
1111 s = tmp;
1114 #ifdef HAVE_REGEX_H
1115 /* add the text to the list store */
1116 gtk_list_store_append(debug_win->store, &iter);
1117 gtk_list_store_set(debug_win->store, &iter, 0, s, 1, level, -1);
1118 #else /* HAVE_REGEX_H */
1119 if(!debug_win->paused && level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
1120 gtk_imhtml_append_text(GTK_IMHTML(debug_win->text), s, 0);
1121 #endif /* !HAVE_REGEX_H */
1123 g_free(s);
1126 static gboolean
1127 pidgin_debug_is_enabled(PurpleDebugLevel level, const char *category)
1129 return (debug_win != NULL &&
1130 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
1133 static PurpleDebugUiOps ops =
1135 pidgin_debug_print,
1136 pidgin_debug_is_enabled,
1137 NULL,
1138 NULL,
1139 NULL,
1140 NULL
1143 PurpleDebugUiOps *
1144 pidgin_debug_get_ui_ops(void)
1146 return &ops;
1149 void *
1150 pidgin_debug_get_handle() {
1151 static int handle;
1153 return &handle;