ChangeLog entries for what I've plucked. Some are better than what they were before.
[pidgin-git.git] / pidgin / gtkdebug.c
blob88eff55526ca2de14af045a9a3679f67ae1b039a
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 # define USE_REGEX 1
43 #else
44 #if GLIB_CHECK_VERSION(2,14,0)
45 # define USE_REGEX 1
46 #endif
47 #endif /* HAVE_REGEX_H */
49 #include <gdk/gdkkeysyms.h>
51 typedef struct
53 GtkWidget *window;
54 GtkWidget *text;
56 GtkListStore *store;
58 gboolean paused;
60 #ifdef USE_REGEX
61 GtkWidget *filter;
62 GtkWidget *expression;
64 gboolean invert;
65 gboolean highlight;
67 guint timer;
68 # ifdef HAVE_REGEX_H
69 regex_t regex;
70 # else
71 GRegex *regex;
72 # endif /* HAVE_REGEX_H */
73 #else
74 GtkWidget *find;
75 #endif /* USE_REGEX */
76 GtkWidget *filterlevel;
77 } DebugWindow;
79 static const char debug_fg_colors[][8] = {
80 "#000000", /**< All debug levels. */
81 "#666666", /**< Misc. */
82 "#000000", /**< Information. */
83 "#660000", /**< Warnings. */
84 "#FF0000", /**< Errors. */
85 "#FF0000", /**< Fatal errors. */
88 static DebugWindow *debug_win = NULL;
89 static guint debug_enabled_timer = 0;
91 #ifdef USE_REGEX
92 static void regex_filter_all(DebugWindow *win);
93 static void regex_show_all(DebugWindow *win);
94 #endif /* USE_REGEX */
96 static gint
97 debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused)
99 purple_prefs_disconnect_by_handle(pidgin_debug_get_handle());
101 #ifdef USE_REGEX
102 if(debug_win->timer != 0) {
103 const gchar *text;
105 purple_timeout_remove(debug_win->timer);
107 text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression));
108 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text);
110 #ifdef HAVE_REGEX_H
111 regfree(&debug_win->regex);
112 #else
113 g_regex_unref(debug_win->regex);
114 #endif /* HAVE_REGEX_H */
115 #endif /* USE_REGEX */
117 /* If the "Save Log" dialog is open then close it */
118 purple_request_close_with_handle(debug_win);
120 g_free(debug_win);
121 debug_win = NULL;
123 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE);
125 return FALSE;
128 static gboolean
129 configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win)
131 if (GTK_WIDGET_VISIBLE(w)) {
132 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/width", event->width);
133 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/height", event->height);
136 return FALSE;
139 #ifndef USE_REGEX
140 struct _find {
141 DebugWindow *window;
142 GtkWidget *entry;
145 static void
146 do_find_cb(GtkWidget *widget, gint response, struct _find *f)
148 switch (response) {
149 case GTK_RESPONSE_OK:
150 gtk_imhtml_search_find(GTK_IMHTML(f->window->text),
151 gtk_entry_get_text(GTK_ENTRY(f->entry)));
152 break;
154 case GTK_RESPONSE_DELETE_EVENT:
155 case GTK_RESPONSE_CLOSE:
156 gtk_imhtml_search_clear(GTK_IMHTML(f->window->text));
157 gtk_widget_destroy(f->window->find);
158 f->window->find = NULL;
159 g_free(f);
160 break;
164 static void
165 find_cb(GtkWidget *w, DebugWindow *win)
167 GtkWidget *hbox, *img, *label;
168 struct _find *f;
170 if(win->find)
172 gtk_window_present(GTK_WINDOW(win->find));
173 return;
176 f = g_malloc(sizeof(struct _find));
177 f->window = win;
178 win->find = gtk_dialog_new_with_buttons(_("Find"),
179 GTK_WINDOW(win->window), GTK_DIALOG_DESTROY_WITH_PARENT,
180 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
181 GTK_STOCK_FIND, GTK_RESPONSE_OK, NULL);
182 gtk_dialog_set_default_response(GTK_DIALOG(win->find),
183 GTK_RESPONSE_OK);
184 g_signal_connect(G_OBJECT(win->find), "response",
185 G_CALLBACK(do_find_cb), f);
187 gtk_container_set_border_width(GTK_CONTAINER(win->find), PIDGIN_HIG_BOX_SPACE);
188 gtk_window_set_resizable(GTK_WINDOW(win->find), FALSE);
189 gtk_dialog_set_has_separator(GTK_DIALOG(win->find), FALSE);
190 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(win->find)->vbox), PIDGIN_HIG_BORDER);
191 gtk_container_set_border_width(
192 GTK_CONTAINER(GTK_DIALOG(win->find)->vbox), PIDGIN_HIG_BOX_SPACE);
194 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
195 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox),
196 hbox);
197 img = gtk_image_new_from_stock(PIDGIN_STOCK_DIALOG_QUESTION,
198 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
199 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
201 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
202 gtk_dialog_set_response_sensitive(GTK_DIALOG(win->find),
203 GTK_RESPONSE_OK, FALSE);
205 label = gtk_label_new(NULL);
206 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Search for:"));
207 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
209 f->entry = gtk_entry_new();
210 gtk_entry_set_activates_default(GTK_ENTRY(f->entry), TRUE);
211 gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(f->entry));
212 g_signal_connect(G_OBJECT(f->entry), "changed",
213 G_CALLBACK(pidgin_set_sensitive_if_input),
214 win->find);
215 gtk_box_pack_start(GTK_BOX(hbox), f->entry, FALSE, FALSE, 0);
217 gtk_widget_show_all(win->find);
218 gtk_widget_grab_focus(f->entry);
220 #endif /* USE_REGEX */
222 static void
223 save_writefile_cb(void *user_data, const char *filename)
225 DebugWindow *win = (DebugWindow *)user_data;
226 FILE *fp;
227 char *tmp;
229 if ((fp = g_fopen(filename, "w+")) == NULL) {
230 purple_notify_error(win, NULL, _("Unable to open file."), NULL);
231 return;
234 tmp = gtk_imhtml_get_text(GTK_IMHTML(win->text), NULL, NULL);
235 fprintf(fp, "Pidgin Debug Log : %s\n", purple_date_format_full(NULL));
236 fprintf(fp, "%s", tmp);
237 g_free(tmp);
239 fclose(fp);
242 static void
243 save_cb(GtkWidget *w, DebugWindow *win)
245 purple_request_file(win, _("Save Debug Log"), "purple-debug.log", TRUE,
246 G_CALLBACK(save_writefile_cb), NULL,
247 NULL, NULL, NULL,
248 win);
251 static void
252 clear_cb(GtkWidget *w, DebugWindow *win)
254 gtk_imhtml_clear(GTK_IMHTML(win->text));
256 #ifdef USE_REGEX
257 gtk_list_store_clear(win->store);
258 #endif /* USE_REGEX */
261 static void
262 pause_cb(GtkWidget *w, DebugWindow *win)
264 win->paused = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(w));
266 #ifdef USE_REGEX
267 if(!win->paused) {
268 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
269 regex_filter_all(win);
270 else
271 regex_show_all(win);
273 #endif /* USE_REGEX */
276 /******************************************************************************
277 * regex stuff
278 *****************************************************************************/
279 #ifdef USE_REGEX
280 static void
281 regex_clear_color(GtkWidget *w) {
282 gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL);
285 static void
286 regex_change_color(GtkWidget *w, guint16 r, guint16 g, guint16 b) {
287 GdkColor color;
289 color.red = r;
290 color.green = g;
291 color.blue = b;
293 gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color);
296 static void
297 regex_highlight_clear(DebugWindow *win) {
298 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
299 GtkTextIter s, e;
301 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &s);
302 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &e);
303 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "regex", &s, &e);
306 static void
307 regex_match(DebugWindow *win, const gchar *text) {
308 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
309 #ifdef HAVE_REGEX_H
310 regmatch_t matches[4]; /* adjust if necessary */
311 size_t n_matches = sizeof(matches) / sizeof(matches[0]);
312 gint inverted;
313 #else
314 GMatchInfo *match_info;
315 #endif /* HAVE_REGEX_H */
316 gchar *plaintext;
318 if(!text)
319 return;
321 /* I don't like having to do this, but we need it for highlighting. Plus
322 * it makes the ^ and $ operators work :)
324 plaintext = purple_markup_strip_html(text);
326 /* we do a first pass to see if it matches at all. If it does we append
327 * it, and work out the offsets to highlight.
329 #ifdef HAVE_REGEX_H
330 inverted = (win->invert) ? REG_NOMATCH : 0;
331 if(regexec(&win->regex, plaintext, n_matches, matches, 0) == inverted) {
332 #else
333 if(g_regex_match(win->regex, plaintext, 0, &match_info) != win->invert) {
334 #endif /* HAVE_REGEX_H */
335 gchar *p = plaintext;
336 GtkTextIter ins;
337 gint i, offset = 0;
339 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins,
340 gtk_text_buffer_get_insert(imhtml->text_buffer));
341 i = gtk_text_iter_get_offset(&ins);
343 gtk_imhtml_append_text(imhtml, text, 0);
345 /* If we're not highlighting or the expression is inverted, we're
346 * done and move on.
348 if(!win->highlight || win->invert) {
349 g_free(plaintext);
350 #ifndef HAVE_REGEX_H
351 g_match_info_free(match_info);
352 #endif
353 return;
356 /* we use a do-while to highlight the first match, and then continue
357 * if necessary...
359 #ifdef HAVE_REGEX_H
360 do {
361 size_t m;
363 for(m = 0; m < n_matches; m++) {
364 GtkTextIter ms, me;
366 if(matches[m].rm_eo == -1)
367 break;
369 i += offset;
371 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ms,
372 i + matches[m].rm_so);
373 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &me,
374 i + matches[m].rm_eo);
375 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "regex",
376 &ms, &me);
377 offset = matches[m].rm_eo;
380 p += offset;
381 } while(regexec(&win->regex, p, n_matches, matches, REG_NOTBOL) == inverted);
382 #else
385 gint m;
386 gint start_pos, end_pos;
387 GtkTextIter ms, me;
389 if (!g_match_info_matches(match_info))
390 break;
392 for (m = 0; m < g_match_info_get_match_count(match_info); m++)
394 if (m == 1)
395 continue;
397 g_match_info_fetch_pos(match_info, m, &start_pos, &end_pos);
399 if (end_pos == -1)
400 break;
402 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ms,
403 i + start_pos);
404 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &me,
405 i + end_pos);
406 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "regex",
407 &ms, &me);
408 offset = end_pos;
411 g_match_info_free(match_info);
412 p += offset;
413 i += offset;
414 } while (g_regex_match(win->regex, p, G_REGEX_MATCH_NOTBOL, &match_info) != win->invert);
415 g_match_info_free(match_info);
416 #endif /* HAVE_REGEX_H */
419 g_free(plaintext);
422 static gboolean
423 regex_filter_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
424 gpointer data)
426 DebugWindow *win = (DebugWindow *)data;
427 gchar *text;
428 PurpleDebugLevel level;
430 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
432 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
433 regex_match(win, text);
435 g_free(text);
437 return FALSE;
440 static void
441 regex_filter_all(DebugWindow *win) {
442 gtk_imhtml_clear(GTK_IMHTML(win->text));
444 if(win->highlight)
445 regex_highlight_clear(win);
447 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_filter_all_cb,
448 win);
451 static gboolean
452 regex_show_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
453 gpointer data)
455 DebugWindow *win = (DebugWindow *)data;
456 gchar *text;
457 PurpleDebugLevel level;
459 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
460 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
461 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
462 g_free(text);
464 return FALSE;
467 static void
468 regex_show_all(DebugWindow *win) {
469 gtk_imhtml_clear(GTK_IMHTML(win->text));
471 if(win->highlight)
472 regex_highlight_clear(win);
474 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_show_all_cb,
475 win);
478 static void
479 regex_compile(DebugWindow *win) {
480 const gchar *text;
482 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
484 if(text == NULL || *text == '\0') {
485 regex_clear_color(win->expression);
486 gtk_widget_set_sensitive(win->filter, FALSE);
487 return;
490 #ifdef HAVE_REGEX_H
491 regfree(&win->regex);
492 if(regcomp(&win->regex, text, REG_EXTENDED | REG_ICASE) != 0) {
493 #else
494 if (win->regex)
495 g_regex_unref(win->regex);
496 win->regex = g_regex_new(text, G_REGEX_EXTENDED | G_REGEX_CASELESS, 0, NULL);
497 if(win->regex == NULL) {
498 #endif
499 /* failed to compile */
500 regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF);
501 gtk_widget_set_sensitive(win->filter, FALSE);
502 } else {
503 /* compiled successfully */
504 regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF);
505 gtk_widget_set_sensitive(win->filter, TRUE);
508 /* we check if the filter is on in case it was only of the options that
509 * got changed, and not the expression.
511 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
512 regex_filter_all(win);
515 static void
516 regex_pref_filter_cb(const gchar *name, PurplePrefType type,
517 gconstpointer val, gpointer data)
519 DebugWindow *win = (DebugWindow *)data;
520 gboolean active = GPOINTER_TO_INT(val), current;
522 if(!win || !win->window)
523 return;
525 current = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter));
526 if(active != current)
527 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), active);
530 static void
531 regex_pref_expression_cb(const gchar *name, PurplePrefType type,
532 gconstpointer val, gpointer data)
534 DebugWindow *win = (DebugWindow *)data;
535 const gchar *exp = (const gchar *)val;
537 gtk_entry_set_text(GTK_ENTRY(win->expression), exp);
540 static void
541 regex_pref_invert_cb(const gchar *name, PurplePrefType type,
542 gconstpointer val, gpointer data)
544 DebugWindow *win = (DebugWindow *)data;
545 gboolean active = GPOINTER_TO_INT(val);
547 win->invert = active;
549 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
550 regex_filter_all(win);
553 static void
554 regex_pref_highlight_cb(const gchar *name, PurplePrefType type,
555 gconstpointer val, gpointer data)
557 DebugWindow *win = (DebugWindow *)data;
558 gboolean active = GPOINTER_TO_INT(val);
560 win->highlight = active;
562 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
563 regex_filter_all(win);
566 static void
567 regex_row_changed_cb(GtkTreeModel *model, GtkTreePath *path,
568 GtkTreeIter *iter, DebugWindow *win)
570 gchar *text;
571 PurpleDebugLevel level;
573 if(!win || !win->window)
574 return;
576 /* If the debug window is paused, we just return since it's in the store.
577 * We don't call regex_match because it doesn't make sense to check the
578 * string if it's paused. When we unpause we clear the imhtml and
579 * reiterate over the store to handle matches that were outputted when
580 * we were paused.
582 if(win->paused)
583 return;
585 gtk_tree_model_get(model, iter, 0, &text, 1, &level, -1);
587 if (level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel")) {
588 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) {
589 regex_match(win, text);
590 } else {
591 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
595 g_free(text);
598 static gboolean
599 regex_timer_cb(DebugWindow *win) {
600 const gchar *text;
602 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
603 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/debug/regex", text);
605 win->timer = 0;
607 return FALSE;
610 static void
611 regex_changed_cb(GtkWidget *w, DebugWindow *win) {
612 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter))) {
613 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter),
614 FALSE);
617 if(win->timer == 0)
618 win->timer = purple_timeout_add_seconds(5, (GSourceFunc)regex_timer_cb, win);
620 regex_compile(win);
623 static void
624 regex_key_release_cb(GtkWidget *w, GdkEventKey *e, DebugWindow *win) {
625 if(e->keyval == GDK_Return &&
626 GTK_WIDGET_IS_SENSITIVE(win->filter) &&
627 !gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
629 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter), TRUE);
633 static void
634 regex_menu_cb(GtkWidget *item, const gchar *pref) {
635 gboolean active;
637 active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item));
639 purple_prefs_set_bool(pref, active);
642 static void
643 regex_popup_cb(GtkEntry *entry, GtkWidget *menu, DebugWindow *win) {
644 pidgin_separator(menu);
645 pidgin_new_check_item(menu, _("Invert"),
646 G_CALLBACK(regex_menu_cb),
647 PIDGIN_PREFS_ROOT "/debug/invert", win->invert);
648 pidgin_new_check_item(menu, _("Highlight matches"),
649 G_CALLBACK(regex_menu_cb),
650 PIDGIN_PREFS_ROOT "/debug/highlight", win->highlight);
653 static void
654 regex_filter_toggled_cb(GtkToggleToolButton *button, DebugWindow *win) {
655 gboolean active;
657 active = gtk_toggle_tool_button_get_active(button);
659 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/filter", active);
661 if(!GTK_IS_IMHTML(win->text))
662 return;
664 if(active)
665 regex_filter_all(win);
666 else
667 regex_show_all(win);
670 static void
671 filter_level_pref_changed(const char *name, PurplePrefType type, gconstpointer value, gpointer data)
673 DebugWindow *win = data;
675 if (GPOINTER_TO_INT(value) != gtk_combo_box_get_active(GTK_COMBO_BOX(win->filterlevel)))
676 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel), GPOINTER_TO_INT(value));
677 if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(win->filter)))
678 regex_filter_all(win);
679 else
680 regex_show_all(win);
682 #endif /* USE_REGEX */
684 static void
685 filter_level_changed_cb(GtkWidget *combo, gpointer null)
687 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/filterlevel",
688 gtk_combo_box_get_active(GTK_COMBO_BOX(combo)));
691 static void
692 toolbar_style_pref_changed_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data)
694 gtk_toolbar_set_style(GTK_TOOLBAR(data), GPOINTER_TO_INT(value));
697 static void
698 toolbar_icon_pref_changed(GtkWidget *item, GtkWidget *toolbar)
700 int style = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user_data"));
701 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/debug/style", style);
704 static gboolean
705 toolbar_context(GtkWidget *toolbar, GdkEventButton *event, gpointer null)
707 GtkWidget *menu, *item;
708 const char *text[3];
709 GtkToolbarStyle value[3];
710 int i;
712 if (!(event->button == 3 && event->type == GDK_BUTTON_PRESS))
713 return FALSE;
715 text[0] = _("_Icon Only"); value[0] = GTK_TOOLBAR_ICONS;
716 text[1] = _("_Text Only"); value[1] = GTK_TOOLBAR_TEXT;
717 text[2] = _("_Both Icon & Text"); value[2] = GTK_TOOLBAR_BOTH_HORIZ;
719 menu = gtk_menu_new();
721 for (i = 0; i < 3; i++) {
722 item = gtk_check_menu_item_new_with_mnemonic(text[i]);
723 g_object_set_data(G_OBJECT(item), "user_data", GINT_TO_POINTER(value[i]));
724 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(toolbar_icon_pref_changed), toolbar);
725 if (value[i] == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style"))
726 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
727 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
730 gtk_widget_show_all(menu);
732 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
733 return FALSE;
736 static DebugWindow *
737 debug_window_new(void)
739 DebugWindow *win;
740 GtkWidget *vbox;
741 GtkWidget *toolbar;
742 GtkWidget *frame;
743 gint width, height;
744 void *handle;
745 GtkToolItem *item;
746 #if !GTK_CHECK_VERSION(2,12,0)
747 GtkTooltips *tooltips;
748 #endif
750 win = g_new0(DebugWindow, 1);
752 width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/width");
753 height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/height");
755 win->window = pidgin_create_window(_("Debug Window"), 0, "debug", TRUE);
756 purple_debug_info("gtkdebug", "Setting dimensions to %d, %d\n",
757 width, height);
759 gtk_window_set_default_size(GTK_WINDOW(win->window), width, height);
761 g_signal_connect(G_OBJECT(win->window), "delete_event",
762 G_CALLBACK(debug_window_destroy), NULL);
763 g_signal_connect(G_OBJECT(win->window), "configure_event",
764 G_CALLBACK(configure_cb), win);
766 handle = pidgin_debug_get_handle();
768 #ifdef USE_REGEX
769 /* the list store for all the messages */
770 win->store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
772 /* row-changed gets called when we do gtk_list_store_set, and row-inserted
773 * gets called with gtk_list_store_append, which is a
774 * completely empty row. So we just ignore row-inserted, and deal with row
775 * changed. -Gary
777 g_signal_connect(G_OBJECT(win->store), "row-changed",
778 G_CALLBACK(regex_row_changed_cb), win);
780 #endif /* USE_REGEX */
782 /* Setup the vbox */
783 vbox = gtk_vbox_new(FALSE, 0);
784 gtk_container_add(GTK_CONTAINER(win->window), vbox);
786 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/toolbar")) {
787 /* Setup our top button bar thingie. */
788 toolbar = gtk_toolbar_new();
789 #if !GTK_CHECK_VERSION(2,12,0)
790 tooltips = gtk_tooltips_new();
791 #endif
792 #if !GTK_CHECK_VERSION(2,14,0)
793 gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE);
794 #endif
795 gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), TRUE);
796 g_signal_connect(G_OBJECT(toolbar), "button-press-event", G_CALLBACK(toolbar_context), win);
798 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar),
799 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/style"));
800 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/style",
801 toolbar_style_pref_changed_cb, toolbar);
802 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar),
803 GTK_ICON_SIZE_SMALL_TOOLBAR);
805 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
807 #ifndef USE_REGEX
808 /* Find button */
809 item = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
810 gtk_tool_item_set_is_important(item, TRUE);
811 #if GTK_CHECK_VERSION(2,12,0)
812 gtk_tool_item_set_tooltip_text(item, _("Find"));
813 #else
814 gtk_tool_item_set_tooltip(item, tooltips, _("Find"), NULL);
815 #endif
816 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(find_cb), win);
817 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
818 #endif /* USE_REGEX */
820 /* Save */
821 item = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
822 gtk_tool_item_set_is_important(item, TRUE);
823 #if GTK_CHECK_VERSION(2,12,0)
824 gtk_tool_item_set_tooltip_text(item, _("Save"));
825 #else
826 gtk_tool_item_set_tooltip(item, tooltips, _("Save"), NULL);
827 #endif
828 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(save_cb), win);
829 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
831 /* Clear button */
832 item = gtk_tool_button_new_from_stock(GTK_STOCK_CLEAR);
833 gtk_tool_item_set_is_important(item, TRUE);
834 #if GTK_CHECK_VERSION(2,12,0)
835 gtk_tool_item_set_tooltip_text(item, _("Clear"));
836 #else
837 gtk_tool_item_set_tooltip(item, tooltips, _("Clear"), NULL);
838 #endif
839 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(clear_cb), win);
840 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
842 item = gtk_separator_tool_item_new();
843 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
845 /* Pause */
846 item = gtk_toggle_tool_button_new_from_stock(PIDGIN_STOCK_PAUSE);
847 gtk_tool_item_set_is_important(item, TRUE);
848 #if GTK_CHECK_VERSION(2,12,0)
849 gtk_tool_item_set_tooltip_text(item, _("Pause"));
850 #else
851 gtk_tool_item_set_tooltip(item, tooltips, _("Pause"), NULL);
852 #endif
853 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(pause_cb), win);
854 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
856 #ifdef USE_REGEX
857 /* regex stuff */
858 item = gtk_separator_tool_item_new();
859 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
861 /* regex toggle button */
862 item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_FIND);
863 gtk_tool_item_set_is_important(item, TRUE);
864 win->filter = GTK_WIDGET(item);
865 gtk_tool_button_set_label(GTK_TOOL_BUTTON(win->filter), _("Filter"));
866 #if GTK_CHECK_VERSION(2,12,0)
867 gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(win->filter), _("Filter"));
868 #else
869 gtk_tooltips_set_tip(tooltips, win->filter, _("Filter"), NULL);
870 #endif
871 g_signal_connect(G_OBJECT(win->filter), "clicked", G_CALLBACK(regex_filter_toggled_cb), win);
872 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(win->filter));
874 /* we purposely disable the toggle button here in case
875 * /purple/gtk/debug/expression has an empty string. If it does not have
876 * an empty string, the change signal will get called and make the
877 * toggle button sensitive.
879 gtk_widget_set_sensitive(win->filter, FALSE);
880 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(win->filter),
881 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/filter"));
882 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filter",
883 regex_pref_filter_cb, win);
885 /* regex entry */
886 win->expression = gtk_entry_new();
887 item = gtk_tool_item_new();
888 #if GTK_CHECK_VERSION(2,12,0)
889 gtk_widget_set_tooltip_text(win->expression, _("Right click for more options."));
890 #else
891 gtk_tooltips_set_tip(tooltips, win->expression, _("Right click for more options."), NULL);
892 #endif
893 gtk_container_add(GTK_CONTAINER(item), GTK_WIDGET(win->expression));
894 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
896 /* this needs to be before the text is set from the pref if we want it
897 * to colorize a stored expression.
899 g_signal_connect(G_OBJECT(win->expression), "changed",
900 G_CALLBACK(regex_changed_cb), win);
901 gtk_entry_set_text(GTK_ENTRY(win->expression),
902 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/debug/regex"));
903 g_signal_connect(G_OBJECT(win->expression), "populate-popup",
904 G_CALLBACK(regex_popup_cb), win);
905 g_signal_connect(G_OBJECT(win->expression), "key-release-event",
906 G_CALLBACK(regex_key_release_cb), win);
907 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/regex",
908 regex_pref_expression_cb, win);
910 /* connect the rest of our pref callbacks */
911 win->invert = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/invert");
912 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/invert",
913 regex_pref_invert_cb, win);
915 win->highlight = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/highlight");
916 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/highlight",
917 regex_pref_highlight_cb, win);
919 #endif /* USE_REGEX */
921 item = gtk_separator_tool_item_new();
922 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
924 item = gtk_tool_item_new();
925 gtk_container_add(GTK_CONTAINER(item), gtk_label_new(_("Level ")));
926 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
928 win->filterlevel = gtk_combo_box_new_text();
929 item = gtk_tool_item_new();
930 #if GTK_CHECK_VERSION(2,12,0)
931 gtk_widget_set_tooltip_text(win->filterlevel, _("Select the debug filter level."));
932 #else
933 gtk_tooltips_set_tip(tooltips, win->filterlevel, _("Select the debug filter level."), NULL);
934 #endif
935 gtk_container_add(GTK_CONTAINER(item), win->filterlevel);
936 gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(item));
938 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("All"));
939 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Misc"));
940 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Info"));
941 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Warning"));
942 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Error "));
943 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Fatal Error"));
944 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel),
945 purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"));
946 #ifdef USE_REGEX
947 purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/debug/filterlevel",
948 filter_level_pref_changed, win);
949 #endif
950 g_signal_connect(G_OBJECT(win->filterlevel), "changed",
951 G_CALLBACK(filter_level_changed_cb), NULL);
954 /* Add the gtkimhtml */
955 frame = pidgin_create_imhtml(FALSE, &win->text, NULL, NULL);
956 gtk_imhtml_set_format_functions(GTK_IMHTML(win->text),
957 GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY ^ GTK_IMHTML_IMAGE);
958 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
959 gtk_widget_show(frame);
961 #ifdef USE_REGEX
962 /* add the tag for regex highlighting */
963 gtk_text_buffer_create_tag(GTK_IMHTML(win->text)->text_buffer, "regex",
964 "background", "#FFAFAF",
965 "weight", "bold",
966 NULL);
967 #endif /* USE_REGEX */
969 gtk_widget_show_all(win->window);
971 return win;
974 static gboolean
975 debug_enabled_timeout_cb(gpointer data)
977 debug_enabled_timer = 0;
979 if (data)
980 pidgin_debug_window_show();
981 else
982 pidgin_debug_window_hide();
984 return FALSE;
987 static void
988 debug_enabled_cb(const char *name, PurplePrefType type,
989 gconstpointer value, gpointer data)
991 debug_enabled_timer = g_timeout_add(0, debug_enabled_timeout_cb, GINT_TO_POINTER(GPOINTER_TO_INT(value)));
994 static void
995 pidgin_glib_log_handler(const gchar *domain, GLogLevelFlags flags,
996 const gchar *msg, gpointer user_data)
998 PurpleDebugLevel level;
999 char *new_msg = NULL;
1000 char *new_domain = NULL;
1002 if ((flags & G_LOG_LEVEL_ERROR) == G_LOG_LEVEL_ERROR)
1003 level = PURPLE_DEBUG_ERROR;
1004 else if ((flags & G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL)
1005 level = PURPLE_DEBUG_FATAL;
1006 else if ((flags & G_LOG_LEVEL_WARNING) == G_LOG_LEVEL_WARNING)
1007 level = PURPLE_DEBUG_WARNING;
1008 else if ((flags & G_LOG_LEVEL_MESSAGE) == G_LOG_LEVEL_MESSAGE)
1009 level = PURPLE_DEBUG_INFO;
1010 else if ((flags & G_LOG_LEVEL_INFO) == G_LOG_LEVEL_INFO)
1011 level = PURPLE_DEBUG_INFO;
1012 else if ((flags & G_LOG_LEVEL_DEBUG) == G_LOG_LEVEL_DEBUG)
1013 level = PURPLE_DEBUG_MISC;
1014 else
1016 purple_debug_warning("gtkdebug",
1017 "Unknown glib logging level in %d\n", flags);
1019 level = PURPLE_DEBUG_MISC; /* This will never happen. */
1022 if (msg != NULL)
1023 new_msg = purple_utf8_try_convert(msg);
1025 if (domain != NULL)
1026 new_domain = purple_utf8_try_convert(domain);
1028 if (new_msg != NULL)
1030 purple_debug(level, (new_domain != NULL ? new_domain : "g_log"),
1031 "%s\n", new_msg);
1033 g_free(new_msg);
1036 g_free(new_domain);
1039 #ifdef _WIN32
1040 static void
1041 pidgin_glib_dummy_print_handler(const gchar *string)
1044 #endif
1046 void
1047 pidgin_debug_init(void)
1049 /* Debug window preferences. */
1051 * NOTE: This must be set before prefs are loaded, and the callbacks
1052 * set after they are loaded, since prefs sets the enabled
1053 * preference here and that loads the window, which calls the
1054 * configure event, which overrides the width and height! :P
1057 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/debug");
1059 /* Controls printing to the debug window */
1060 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/enabled", FALSE);
1061 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/filterlevel", PURPLE_DEBUG_ALL);
1062 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/style", GTK_TOOLBAR_BOTH_HORIZ);
1064 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/toolbar", TRUE);
1065 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/width", 450);
1066 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/debug/height", 250);
1068 #ifdef USE_REGEX
1069 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/debug/regex", "");
1070 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/filter", FALSE);
1071 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/invert", FALSE);
1072 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/case_insensitive", FALSE);
1073 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/debug/highlight", FALSE);
1074 #endif /* USE_REGEX */
1076 purple_prefs_connect_callback(NULL, PIDGIN_PREFS_ROOT "/debug/enabled",
1077 debug_enabled_cb, NULL);
1079 #define REGISTER_G_LOG_HANDLER(name) \
1080 g_log_set_handler((name), G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL \
1081 | G_LOG_FLAG_RECURSION, \
1082 pidgin_glib_log_handler, NULL)
1084 /* Register the glib/gtk log handlers. */
1085 REGISTER_G_LOG_HANDLER(NULL);
1086 REGISTER_G_LOG_HANDLER("Gdk");
1087 REGISTER_G_LOG_HANDLER("Gtk");
1088 REGISTER_G_LOG_HANDLER("GdkPixbuf");
1089 REGISTER_G_LOG_HANDLER("GLib");
1090 REGISTER_G_LOG_HANDLER("GModule");
1091 REGISTER_G_LOG_HANDLER("GLib-GObject");
1092 REGISTER_G_LOG_HANDLER("GThread");
1093 #ifdef USE_GSTREAMER
1094 REGISTER_G_LOG_HANDLER("GStreamer");
1095 #endif
1097 #ifdef _WIN32
1098 if (!purple_debug_is_enabled())
1099 g_set_print_handler(pidgin_glib_dummy_print_handler);
1100 #endif
1103 void
1104 pidgin_debug_uninit(void)
1106 purple_debug_set_ui_ops(NULL);
1108 if (debug_enabled_timer != 0)
1109 g_source_remove(debug_enabled_timer);
1112 void
1113 pidgin_debug_window_show(void)
1115 if (debug_win == NULL)
1116 debug_win = debug_window_new();
1118 gtk_widget_show(debug_win->window);
1120 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/debug/enabled", TRUE);
1123 void
1124 pidgin_debug_window_hide(void)
1126 if (debug_win != NULL) {
1127 gtk_widget_destroy(debug_win->window);
1128 debug_window_destroy(NULL, NULL, NULL);
1132 static void
1133 pidgin_debug_print(PurpleDebugLevel level, const char *category,
1134 const char *arg_s)
1136 #ifdef USE_REGEX
1137 GtkTreeIter iter;
1138 #endif /* USE_REGEX */
1139 gchar *ts_s;
1140 gchar *esc_s, *cat_s, *tmp, *s;
1141 const char *mdate;
1142 time_t mtime;
1144 if (debug_win == NULL ||
1145 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"))
1147 return;
1150 mtime = time(NULL);
1151 mdate = purple_utf8_strftime("%H:%M:%S", localtime(&mtime));
1152 ts_s = g_strdup_printf("(%s) ", mdate);
1153 if (category == NULL)
1154 cat_s = g_strdup("");
1155 else
1156 cat_s = g_strdup_printf("<b>%s:</b> ", category);
1158 esc_s = g_markup_escape_text(arg_s, -1);
1160 s = g_strdup_printf("<font color=\"%s\">%s%s%s</font>",
1161 debug_fg_colors[level], ts_s, cat_s, esc_s);
1163 g_free(ts_s);
1164 g_free(cat_s);
1165 g_free(esc_s);
1167 tmp = purple_utf8_try_convert(s);
1168 g_free(s);
1169 s = tmp;
1171 if (level == PURPLE_DEBUG_FATAL) {
1172 tmp = g_strdup_printf("<b>%s</b>", s);
1173 g_free(s);
1174 s = tmp;
1177 #ifdef USE_REGEX
1178 /* add the text to the list store */
1179 gtk_list_store_append(debug_win->store, &iter);
1180 gtk_list_store_set(debug_win->store, &iter, 0, s, 1, level, -1);
1181 #else /* USE_REGEX */
1182 if(!debug_win->paused && level >= purple_prefs_get_int(PIDGIN_PREFS_ROOT "/debug/filterlevel"))
1183 gtk_imhtml_append_text(GTK_IMHTML(debug_win->text), s, 0);
1184 #endif /* !USE_REGEX */
1186 g_free(s);
1189 static gboolean
1190 pidgin_debug_is_enabled(PurpleDebugLevel level, const char *category)
1192 return (debug_win != NULL &&
1193 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/debug/enabled"));
1196 static PurpleDebugUiOps ops =
1198 pidgin_debug_print,
1199 pidgin_debug_is_enabled,
1200 NULL,
1201 NULL,
1202 NULL,
1203 NULL
1206 PurpleDebugUiOps *
1207 pidgin_debug_get_ui_ops(void)
1209 return &ops;
1212 void *
1213 pidgin_debug_get_handle() {
1214 static int handle;
1216 return &handle;