comparison gtk/gtkdebug.c @ 20389:e354528c4163

propagate from branch 'im.pidgin.gaim' (head 70ac931e4936c7916eec18a07fe46a0af0fd7403) to branch 'im.pidgin.rlaager.merging.soc-msnp13-to-svn18164' (head 5b5cde92182d2a922a8e7e6c2308342a5490a8c9)
author Richard Laager <rlaager@wiktel.com>
date Sun, 15 Apr 2007 02:10:37 +0000
parents aca9a7b62a23
children
comparison
equal deleted inserted replaced
19796:21cb7a79ac7f 20389:e354528c4163
1 /**
2 * @file gtkdebug.c GTK+ Debug API
3 * @ingroup gtkui
4 *
5 * gaim
6 *
7 * Gaim is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25 #include "internal.h"
26 #include "gtkgaim.h"
27
28 #include "notify.h"
29 #include "prefs.h"
30 #include "request.h"
31 #include "util.h"
32
33 #include "gtkdebug.h"
34 #include "gtkdialogs.h"
35 #include "gtkimhtml.h"
36 #include "gtkutils.h"
37 #include "gaimstock.h"
38
39 #ifdef HAVE_REGEX_H
40 # include <regex.h>
41 #endif /* HAVE_REGEX_H */
42
43 #include <gdk/gdkkeysyms.h>
44
45 typedef struct
46 {
47 GtkWidget *window;
48 GtkWidget *text;
49
50 GtkListStore *store;
51
52 gboolean timestamps;
53 gboolean paused;
54
55 #ifdef HAVE_REGEX_H
56 GtkWidget *filter;
57 GtkWidget *expression;
58
59 gboolean invert;
60 gboolean highlight;
61
62 guint timer;
63
64 regex_t regex;
65 #else
66 GtkWidget *find;
67 #endif /* HAVE_REGEX_H */
68 GtkWidget *filterlevel;
69 } DebugWindow;
70
71 static 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. */
78 };
79
80 static DebugWindow *debug_win = NULL;
81
82 #ifdef HAVE_REGEX_H
83 static void regex_filter_all(DebugWindow *win);
84 static void regex_show_all(DebugWindow *win);
85 #endif /* HAVE_REGEX_H */
86
87 static gint
88 debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused)
89 {
90 gaim_prefs_disconnect_by_handle(gaim_gtk_debug_get_handle());
91
92 #ifdef HAVE_REGEX_H
93 if(debug_win->timer != 0) {
94 const gchar *text;
95
96 g_source_remove(debug_win->timer);
97
98 text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression));
99 gaim_prefs_set_string("/gaim/gtk/debug/regex", text);
100 }
101
102 regfree(&debug_win->regex);
103 #endif
104
105 /* If the "Save Log" dialog is open then close it */
106 gaim_request_close_with_handle(debug_win);
107
108 g_free(debug_win);
109 debug_win = NULL;
110
111 gaim_prefs_set_bool("/gaim/gtk/debug/enabled", FALSE);
112
113 return FALSE;
114 }
115
116 static gboolean
117 configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win)
118 {
119 if (GTK_WIDGET_VISIBLE(w)) {
120 gaim_prefs_set_int("/gaim/gtk/debug/width", event->width);
121 gaim_prefs_set_int("/gaim/gtk/debug/height", event->height);
122 }
123
124 return FALSE;
125 }
126
127 #ifndef HAVE_REGEX_H
128 struct _find {
129 DebugWindow *window;
130 GtkWidget *entry;
131 };
132
133 static void
134 do_find_cb(GtkWidget *widget, gint response, struct _find *f)
135 {
136 switch (response) {
137 case GTK_RESPONSE_OK:
138 gtk_imhtml_search_find(GTK_IMHTML(f->window->text),
139 gtk_entry_get_text(GTK_ENTRY(f->entry)));
140 break;
141
142 case GTK_RESPONSE_DELETE_EVENT:
143 case GTK_RESPONSE_CLOSE:
144 gtk_imhtml_search_clear(GTK_IMHTML(f->window->text));
145 gtk_widget_destroy(f->window->find);
146 f->window->find = NULL;
147 g_free(f);
148 break;
149 }
150 }
151
152 static void
153 find_cb(GtkWidget *w, DebugWindow *win)
154 {
155 GtkWidget *hbox, *img, *label;
156 struct _find *f;
157
158 if(win->find)
159 {
160 gtk_window_present(GTK_WINDOW(win->find));
161 return;
162 }
163
164 f = g_malloc(sizeof(struct _find));
165 f->window = win;
166 win->find = gtk_dialog_new_with_buttons(_("Find"),
167 GTK_WINDOW(win->window), GTK_DIALOG_DESTROY_WITH_PARENT,
168 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
169 GTK_STOCK_FIND, GTK_RESPONSE_OK, NULL);
170 gtk_dialog_set_default_response(GTK_DIALOG(win->find),
171 GTK_RESPONSE_OK);
172 g_signal_connect(G_OBJECT(win->find), "response",
173 G_CALLBACK(do_find_cb), f);
174
175 gtk_container_set_border_width(GTK_CONTAINER(win->find), GAIM_HIG_BOX_SPACE);
176 gtk_window_set_resizable(GTK_WINDOW(win->find), FALSE);
177 gtk_dialog_set_has_separator(GTK_DIALOG(win->find), FALSE);
178 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(win->find)->vbox), GAIM_HIG_BORDER);
179 gtk_container_set_border_width(
180 GTK_CONTAINER(GTK_DIALOG(win->find)->vbox), GAIM_HIG_BOX_SPACE);
181
182 hbox = gtk_hbox_new(FALSE, GAIM_HIG_BORDER);
183 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win->find)->vbox),
184 hbox);
185 img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
186 GTK_ICON_SIZE_DIALOG);
187 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
188
189 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
190 gtk_dialog_set_response_sensitive(GTK_DIALOG(win->find),
191 GTK_RESPONSE_OK, FALSE);
192
193 label = gtk_label_new(NULL);
194 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Search for:"));
195 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
196
197 f->entry = gtk_entry_new();
198 gtk_entry_set_activates_default(GTK_ENTRY(f->entry), TRUE);
199 gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(f->entry));
200 g_signal_connect(G_OBJECT(f->entry), "changed",
201 G_CALLBACK(gaim_gtk_set_sensitive_if_input),
202 win->find);
203 gtk_box_pack_start(GTK_BOX(hbox), f->entry, FALSE, FALSE, 0);
204
205 gtk_widget_show_all(win->find);
206 gtk_widget_grab_focus(f->entry);
207 }
208 #endif /* HAVE_REGEX_H */
209
210 static void
211 save_writefile_cb(void *user_data, const char *filename)
212 {
213 DebugWindow *win = (DebugWindow *)user_data;
214 FILE *fp;
215 char *tmp;
216
217 if ((fp = g_fopen(filename, "w+")) == NULL) {
218 gaim_notify_error(win, NULL, _("Unable to open file."), NULL);
219 return;
220 }
221
222 tmp = gtk_imhtml_get_text(GTK_IMHTML(win->text), NULL, NULL);
223 fprintf(fp, "Gaim Debug Log : %s\n", gaim_date_format_full(NULL));
224 fprintf(fp, "%s", tmp);
225 g_free(tmp);
226
227 fclose(fp);
228 }
229
230 static void
231 save_cb(GtkWidget *w, DebugWindow *win)
232 {
233 gaim_request_file(win, _("Save Debug Log"), "gaim-debug.log", TRUE,
234 G_CALLBACK(save_writefile_cb), NULL, win);
235 }
236
237 static void
238 clear_cb(GtkWidget *w, DebugWindow *win)
239 {
240 gtk_imhtml_clear(GTK_IMHTML(win->text));
241
242 #ifdef HAVE_REGEX_H
243 gtk_list_store_clear(win->store);
244 #endif /* HAVE_REGEX_H */
245 }
246
247 static void
248 pause_cb(GtkWidget *w, DebugWindow *win)
249 {
250 win->paused = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
251
252 #ifdef HAVE_REGEX_H
253 if(!win->paused) {
254 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
255 regex_filter_all(win);
256 else
257 regex_show_all(win);
258 }
259 #endif /* HAVE_REGEX_H */
260 }
261
262 static void
263 timestamps_cb(GtkWidget *w, DebugWindow *win)
264 {
265 win->timestamps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
266
267 gaim_prefs_set_bool("/core/debug/timestamps", win->timestamps);
268 }
269
270 static void
271 timestamps_pref_cb(const char *name, GaimPrefType type,
272 gconstpointer value, gpointer data)
273 {
274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data), GPOINTER_TO_INT(value));
275 }
276
277 /******************************************************************************
278 * regex stuff
279 *****************************************************************************/
280 #ifdef HAVE_REGEX_H
281 static void
282 regex_clear_color(GtkWidget *w) {
283 gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL);
284 }
285
286 static void
287 regex_change_color(GtkWidget *w, guint16 r, guint16 g, guint16 b) {
288 GdkColor color;
289
290 color.red = r;
291 color.green = g;
292 color.blue = b;
293
294 gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color);
295 }
296
297 static void
298 regex_highlight_clear(DebugWindow *win) {
299 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
300 GtkTextIter s, e;
301
302 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &s);
303 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &e);
304 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "regex", &s, &e);
305 }
306
307 static void
308 regex_match(DebugWindow *win, const gchar *text) {
309 GtkIMHtml *imhtml = GTK_IMHTML(win->text);
310 regmatch_t matches[4]; /* adjust if necessary */
311 size_t n_matches = sizeof(matches) / sizeof(matches[0]);
312 gchar *plaintext;
313 gint inverted;
314
315 if(!text)
316 return;
317
318 inverted = (win->invert) ? REG_NOMATCH : 0;
319
320 /* I don't like having to do this, but we need it for highlighting. Plus
321 * it makes the ^ and $ operators work :)
322 */
323 plaintext = gaim_markup_strip_html(text);
324
325 /* we do a first pass to see if it matches at all. If it does we append
326 * it, and work out the offsets to highlight.
327 */
328 if(regexec(&win->regex, plaintext, n_matches, matches, 0) == inverted) {
329 GtkTextIter ins;
330 gchar *p = plaintext;
331 gint i, offset = 0;
332
333 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins,
334 gtk_text_buffer_get_insert(imhtml->text_buffer));
335 i = gtk_text_iter_get_offset(&ins);
336
337 gtk_imhtml_append_text(imhtml, text, 0);
338
339 /* If we're not highlighting or the expression is inverted, we're
340 * done and move on.
341 */
342 if(!win->highlight || inverted == REG_NOMATCH) {
343 g_free(plaintext);
344 return;
345 }
346
347 /* we use a do-while to highlight the first match, and then continue
348 * if necessary...
349 */
350 do {
351 size_t m;
352
353 for(m = 0; m < n_matches; m++) {
354 GtkTextIter ms, me;
355
356 if(matches[m].rm_eo == -1)
357 break;
358
359 i += offset;
360
361 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ms,
362 i + matches[m].rm_so);
363 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &me,
364 i + matches[m].rm_eo);
365 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "regex",
366 &ms, &me);
367 offset = matches[m].rm_eo;
368 }
369
370 p += offset;
371 } while(regexec(&win->regex, p, n_matches, matches, REG_NOTBOL) == inverted);
372 }
373
374 g_free(plaintext);
375 }
376
377 static gboolean
378 regex_filter_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
379 gpointer data)
380 {
381 DebugWindow *win = (DebugWindow *)data;
382 gchar *text;
383 GaimDebugLevel level;
384
385 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
386
387 if (level >= gaim_prefs_get_int("/gaim/gtk/debug/filterlevel"))
388 regex_match(win, text);
389
390 g_free(text);
391
392 return FALSE;
393 }
394
395 static void
396 regex_filter_all(DebugWindow *win) {
397 gtk_imhtml_clear(GTK_IMHTML(win->text));
398
399 if(win->highlight)
400 regex_highlight_clear(win);
401
402 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_filter_all_cb,
403 win);
404 }
405
406 static gboolean
407 regex_show_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter,
408 gpointer data)
409 {
410 DebugWindow *win = (DebugWindow *)data;
411 gchar *text;
412 GaimDebugLevel level;
413
414 gtk_tree_model_get(m, iter, 0, &text, 1, &level, -1);
415 if (level >= gaim_prefs_get_int("/gaim/gtk/debug/filterlevel"))
416 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
417 g_free(text);
418
419 return FALSE;
420 }
421
422 static void
423 regex_show_all(DebugWindow *win) {
424 gtk_imhtml_clear(GTK_IMHTML(win->text));
425
426 if(win->highlight)
427 regex_highlight_clear(win);
428
429 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_show_all_cb,
430 win);
431 }
432
433 static void
434 regex_compile(DebugWindow *win) {
435 const gchar *text;
436
437 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
438
439 if(text == NULL || *text == '\0') {
440 regex_clear_color(win->expression);
441 gtk_widget_set_sensitive(win->filter, FALSE);
442 return;
443 }
444
445 regfree(&win->regex);
446
447 if(regcomp(&win->regex, text, REG_EXTENDED | REG_ICASE) != 0) {
448 /* failed to compile */
449 regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF);
450 gtk_widget_set_sensitive(win->filter, FALSE);
451 } else {
452 /* compiled successfully */
453 regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF);
454 gtk_widget_set_sensitive(win->filter, TRUE);
455 }
456
457 /* we check if the filter is on in case it was only of the options that
458 * got changed, and not the expression.
459 */
460 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
461 regex_filter_all(win);
462 }
463
464 static void
465 regex_pref_filter_cb(const gchar *name, GaimPrefType type,
466 gconstpointer val, gpointer data)
467 {
468 DebugWindow *win = (DebugWindow *)data;
469 gboolean active = GPOINTER_TO_INT(val), current;
470
471 if(!win || !win->window)
472 return;
473
474 current = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter));
475 if(active != current)
476 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), active);
477 }
478
479 static void
480 regex_pref_expression_cb(const gchar *name, GaimPrefType type,
481 gconstpointer val, gpointer data)
482 {
483 DebugWindow *win = (DebugWindow *)data;
484 const gchar *exp = (const gchar *)val;
485
486 gtk_entry_set_text(GTK_ENTRY(win->expression), exp);
487 }
488
489 static void
490 regex_pref_invert_cb(const gchar *name, GaimPrefType type,
491 gconstpointer val, gpointer data)
492 {
493 DebugWindow *win = (DebugWindow *)data;
494 gboolean active = GPOINTER_TO_INT(val);
495
496 win->invert = active;
497
498 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
499 regex_filter_all(win);
500 }
501
502 static void
503 regex_pref_highlight_cb(const gchar *name, GaimPrefType type,
504 gconstpointer val, gpointer data)
505 {
506 DebugWindow *win = (DebugWindow *)data;
507 gboolean active = GPOINTER_TO_INT(val);
508
509 win->highlight = active;
510
511 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
512 regex_filter_all(win);
513 }
514
515 static void
516 regex_row_changed_cb(GtkTreeModel *model, GtkTreePath *path,
517 GtkTreeIter *iter, DebugWindow *win)
518 {
519 gchar *text;
520 GaimDebugLevel level;
521
522 if(!win || !win->window)
523 return;
524
525 /* If the debug window is paused, we just return since it's in the store.
526 * We don't call regex_match because it doesn't make sense to check the
527 * string if it's paused. When we unpause we clear the imhtml and
528 * reiterate over the store to handle matches that were outputted when
529 * we were paused.
530 */
531 if(win->paused)
532 return;
533
534 gtk_tree_model_get(model, iter, 0, &text, 1, &level, -1);
535
536 if (level >= gaim_prefs_get_int("/gaim/gtk/debug/filterlevel")) {
537 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) {
538 regex_match(win, text);
539 } else {
540 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0);
541 }
542 }
543
544 g_free(text);
545 }
546
547 static gboolean
548 regex_timer_cb(DebugWindow *win) {
549 const gchar *text;
550
551 text = gtk_entry_get_text(GTK_ENTRY(win->expression));
552 gaim_prefs_set_string("/gaim/gtk/debug/regex", text);
553
554 win->timer = 0;
555
556 return FALSE;
557 }
558
559 static void
560 regex_changed_cb(GtkWidget *w, DebugWindow *win) {
561 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) {
562 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter),
563 FALSE);
564 }
565
566 if(win->timer == 0)
567 win->timer = gaim_timeout_add(5000, (GSourceFunc)regex_timer_cb, win);
568
569 regex_compile(win);
570 }
571
572 static void
573 regex_key_release_cb(GtkWidget *w, GdkEventKey *e, DebugWindow *win) {
574 if(e->keyval == GDK_Return &&
575 GTK_WIDGET_IS_SENSITIVE(win->filter) &&
576 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
577 {
578 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), TRUE);
579 }
580 }
581
582 static void
583 regex_menu_cb(GtkWidget *item, const gchar *pref) {
584 gboolean active;
585
586 active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item));
587
588 gaim_prefs_set_bool(pref, active);
589 }
590
591 static void
592 regex_popup_cb(GtkEntry *entry, GtkWidget *menu, DebugWindow *win) {
593 gaim_separator(menu);
594 gaim_new_check_item(menu, _("Invert"),
595 G_CALLBACK(regex_menu_cb),
596 "/gaim/gtk/debug/invert", win->invert);
597 gaim_new_check_item(menu, _("Highlight matches"),
598 G_CALLBACK(regex_menu_cb),
599 "/gaim/gtk/debug/highlight", win->highlight);
600 }
601
602 static void
603 regex_filter_toggled_cb(GtkToggleButton *button, DebugWindow *win) {
604 gboolean active;
605
606 active = gtk_toggle_button_get_active(button);
607
608 gaim_prefs_set_bool("/gaim/gtk/debug/filter", active);
609
610 if(!GTK_IS_IMHTML(win->text))
611 return;
612
613 if(active)
614 regex_filter_all(win);
615 else
616 regex_show_all(win);
617 }
618
619 static void
620 filter_level_pref_changed(const char *name, GaimPrefType type, gconstpointer value, gpointer data)
621 {
622 DebugWindow *win = data;
623
624 if (GPOINTER_TO_INT(value) != gtk_combo_box_get_active(GTK_COMBO_BOX(win->filterlevel)))
625 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel), GPOINTER_TO_INT(value));
626 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)))
627 regex_filter_all(win);
628 else
629 regex_show_all(win);
630 }
631 #endif /* HAVE_REGEX_H */
632
633 static void
634 filter_level_changed_cb(GtkWidget *combo, gpointer null)
635 {
636 gaim_prefs_set_int("/gaim/gtk/debug/filterlevel",
637 gtk_combo_box_get_active(GTK_COMBO_BOX(combo)));
638 }
639
640 static void
641 toolbar_style_pref_changed_cb(const char *name, GaimPrefType type, gconstpointer value, gpointer data)
642 {
643 gtk_toolbar_set_style(GTK_TOOLBAR(data), GPOINTER_TO_INT(value));
644 }
645
646 static void
647 toolbar_icon_pref_changed(GtkWidget *item, GtkWidget *toolbar)
648 {
649 int style = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user_data"));
650 gaim_prefs_set_int("/gaim/gtk/debug/style", style);
651 }
652
653 static gboolean
654 toolbar_context(GtkWidget *toolbar, GdkEventButton *event, gpointer null)
655 {
656 GtkWidget *menu, *item;
657 const char *text[3];
658 GtkToolbarStyle value[3];
659 int i;
660
661 if (!(event->button == 3 && event->type == GDK_BUTTON_PRESS))
662 return FALSE;
663
664 text[0] = _("_Icon Only"); value[0] = GTK_TOOLBAR_ICONS;
665 text[1] = _("_Text Only"); value[1] = GTK_TOOLBAR_TEXT;
666 text[2] = _("_Both Icon & Text"); value[2] = GTK_TOOLBAR_BOTH_HORIZ;
667
668 menu = gtk_menu_new();
669
670 for (i = 0; i < 3; i++) {
671 item = gtk_check_menu_item_new_with_mnemonic(text[i]);
672 g_object_set_data(G_OBJECT(item), "user_data", GINT_TO_POINTER(value[i]));
673 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(toolbar_icon_pref_changed), toolbar);
674 if (value[i] == gaim_prefs_get_int("/gaim/gtk/debug/style"))
675 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
676 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
677 }
678
679 gtk_widget_show_all(menu);
680
681 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
682 return FALSE;
683 }
684
685 static DebugWindow *
686 debug_window_new(void)
687 {
688 DebugWindow *win;
689 GtkWidget *vbox;
690 GtkWidget *toolbar;
691 GtkWidget *frame;
692 GtkWidget *button;
693 GtkWidget *image;
694 gint width, height;
695 void *handle;
696
697 win = g_new0(DebugWindow, 1);
698
699 width = gaim_prefs_get_int("/gaim/gtk/debug/width");
700 height = gaim_prefs_get_int("/gaim/gtk/debug/height");
701
702 GAIM_DIALOG(win->window);
703 gaim_debug_info("gtkdebug", "Setting dimensions to %d, %d\n",
704 width, height);
705
706 gtk_window_set_default_size(GTK_WINDOW(win->window), width, height);
707 gtk_window_set_role(GTK_WINDOW(win->window), "debug");
708 gtk_window_set_title(GTK_WINDOW(win->window), _("Debug Window"));
709
710 g_signal_connect(G_OBJECT(win->window), "delete_event",
711 G_CALLBACK(debug_window_destroy), NULL);
712 g_signal_connect(G_OBJECT(win->window), "configure_event",
713 G_CALLBACK(configure_cb), win);
714
715 handle = gaim_gtk_debug_get_handle();
716
717 #ifdef HAVE_REGEX_H
718 /* the list store for all the messages */
719 win->store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
720
721 /* row-changed gets called when we do gtk_list_store_set, and row-inserted
722 * gets called with gtk_list_store_append, which is a
723 * completely empty row. So we just ignore row-inserted, and deal with row
724 * changed. -Gary
725 */
726 g_signal_connect(G_OBJECT(win->store), "row-changed",
727 G_CALLBACK(regex_row_changed_cb), win);
728
729 #endif /* HAVE_REGEX_H */
730
731 /* Setup the vbox */
732 vbox = gtk_vbox_new(FALSE, 0);
733 gtk_container_add(GTK_CONTAINER(win->window), vbox);
734
735 if (gaim_prefs_get_bool("/gaim/gtk/debug/toolbar")) {
736 /* Setup our top button bar thingie. */
737 toolbar = gtk_toolbar_new();
738 gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE);
739 #if GTK_CHECK_VERSION(2,4,0)
740 gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), TRUE);
741 #endif
742 g_signal_connect(G_OBJECT(toolbar), "button-press-event", G_CALLBACK(toolbar_context), win);
743
744 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar),
745 gaim_prefs_get_int("/gaim/gtk/debug/style"));
746 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/style",
747 toolbar_style_pref_changed_cb, toolbar);
748 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar),
749 GTK_ICON_SIZE_SMALL_TOOLBAR);
750
751 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
752
753 #ifndef HAVE_REGEX_H
754 /* Find button */
755 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_FIND,
756 _("Find"), NULL, G_CALLBACK(find_cb),
757 win, -1);
758 #endif /* HAVE_REGEX_H */
759
760 /* Save */
761 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_SAVE,
762 _("Save"), NULL, G_CALLBACK(save_cb),
763 win, -1);
764
765 /* Clear button */
766 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_CLEAR,
767 _("Clear"), NULL, G_CALLBACK(clear_cb),
768 win, -1);
769
770 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1);
771
772 /* Pause */
773 image = gtk_image_new_from_stock(GAIM_STOCK_PAUSE, GTK_ICON_SIZE_MENU);
774 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
775 GTK_TOOLBAR_CHILD_TOGGLEBUTTON,
776 NULL, _("Pause"), _("Pause"),
777 NULL, image,
778 G_CALLBACK(pause_cb), win);
779
780 /* Timestamps */
781 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
782 GTK_TOOLBAR_CHILD_TOGGLEBUTTON,
783 NULL, _("Timestamps"),
784 _("Timestamps"), NULL, NULL,
785 G_CALLBACK(timestamps_cb),
786 win);
787
788 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
789 gaim_prefs_get_bool("/core/debug/timestamps"));
790
791 gaim_prefs_connect_callback(handle, "/core/debug/timestamps",
792 timestamps_pref_cb, button);
793
794 #ifdef HAVE_REGEX_H
795 /* regex stuff */
796 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1);
797
798 /* regex toggle button */
799 win->filter =
800 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
801 GTK_TOOLBAR_CHILD_TOGGLEBUTTON,
802 NULL, _("Filter"), _("Filter"),
803 NULL, NULL,
804 G_CALLBACK(regex_filter_toggled_cb),
805 win);
806 /* we purposely disable the toggle button here in case
807 * /gaim/gtk/debug/expression has an empty string. If it does not have
808 * an empty string, the change signal will get called and make the
809 * toggle button sensitive.
810 */
811 gtk_widget_set_sensitive(win->filter, FALSE);
812 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter),
813 gaim_prefs_get_bool("/gaim/gtk/debug/filter"));
814 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/filter",
815 regex_pref_filter_cb, win);
816
817 /* regex entry */
818 win->expression = gtk_entry_new();
819 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
820 GTK_TOOLBAR_CHILD_WIDGET, win->expression,
821 NULL, _("Right click for more options."),
822 NULL, NULL, NULL, NULL);
823 /* this needs to be before the text is set from the pref if we want it
824 * to colorize a stored expression.
825 */
826 g_signal_connect(G_OBJECT(win->expression), "changed",
827 G_CALLBACK(regex_changed_cb), win);
828 gtk_entry_set_text(GTK_ENTRY(win->expression),
829 gaim_prefs_get_string("/gaim/gtk/debug/regex"));
830 g_signal_connect(G_OBJECT(win->expression), "populate-popup",
831 G_CALLBACK(regex_popup_cb), win);
832 g_signal_connect(G_OBJECT(win->expression), "key-release-event",
833 G_CALLBACK(regex_key_release_cb), win);
834 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/regex",
835 regex_pref_expression_cb, win);
836
837 /* connect the rest of our pref callbacks */
838 win->invert = gaim_prefs_get_bool("/gaim/gtk/debug/invert");
839 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/invert",
840 regex_pref_invert_cb, win);
841
842 win->highlight = gaim_prefs_get_bool("/gaim/gtk/debug/highlight");
843 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/highlight",
844 regex_pref_highlight_cb, win);
845
846 #endif /* HAVE_REGEX_H */
847
848 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1);
849
850 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
851 GTK_TOOLBAR_CHILD_WIDGET, gtk_label_new(_("Level ")),
852 NULL, _("Select the debug filter level."),
853 NULL, NULL, NULL, NULL);
854
855 win->filterlevel = gtk_combo_box_new_text();
856 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar),
857 GTK_TOOLBAR_CHILD_WIDGET, win->filterlevel,
858 NULL, _("Select the debug filter level."),
859 NULL, NULL, NULL, NULL);
860 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("All"));
861 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Misc"));
862 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Info"));
863 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Warning"));
864 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Error "));
865 gtk_combo_box_append_text(GTK_COMBO_BOX(win->filterlevel), _("Fatal Error"));
866 gtk_combo_box_set_active(GTK_COMBO_BOX(win->filterlevel),
867 gaim_prefs_get_int("/gaim/gtk/debug/filterlevel"));
868 #ifdef HAVE_REGEX_H
869 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/filterlevel",
870 filter_level_pref_changed, win);
871 #endif
872 g_signal_connect(G_OBJECT(win->filterlevel), "changed",
873 G_CALLBACK(filter_level_changed_cb), NULL);
874 }
875
876 /* Add the gtkimhtml */
877 frame = gaim_gtk_create_imhtml(FALSE, &win->text, NULL, NULL);
878 gtk_imhtml_set_format_functions(GTK_IMHTML(win->text),
879 GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY ^ GTK_IMHTML_IMAGE);
880 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
881 gtk_widget_show(frame);
882
883 #ifdef HAVE_REGEX_H
884 /* add the tag for regex highlighting */
885 gtk_text_buffer_create_tag(GTK_IMHTML(win->text)->text_buffer, "regex",
886 "background", "#FFAFAF",
887 "weight", "bold",
888 NULL);
889 #endif /* HAVE_REGEX_H */
890
891 gtk_widget_show_all(win->window);
892
893 return win;
894 }
895
896 static void
897 debug_enabled_cb(const char *name, GaimPrefType type,
898 gconstpointer value, gpointer data)
899 {
900 if (value)
901 gaim_gtk_debug_window_show();
902 else
903 gaim_gtk_debug_window_hide();
904 }
905
906 static void
907 gaim_glib_log_handler(const gchar *domain, GLogLevelFlags flags,
908 const gchar *msg, gpointer user_data)
909 {
910 GaimDebugLevel level;
911 char *new_msg = NULL;
912 char *new_domain = NULL;
913
914 if ((flags & G_LOG_LEVEL_ERROR) == G_LOG_LEVEL_ERROR)
915 level = GAIM_DEBUG_ERROR;
916 else if ((flags & G_LOG_LEVEL_CRITICAL) == G_LOG_LEVEL_CRITICAL)
917 level = GAIM_DEBUG_FATAL;
918 else if ((flags & G_LOG_LEVEL_WARNING) == G_LOG_LEVEL_WARNING)
919 level = GAIM_DEBUG_WARNING;
920 else if ((flags & G_LOG_LEVEL_MESSAGE) == G_LOG_LEVEL_MESSAGE)
921 level = GAIM_DEBUG_INFO;
922 else if ((flags & G_LOG_LEVEL_INFO) == G_LOG_LEVEL_INFO)
923 level = GAIM_DEBUG_INFO;
924 else if ((flags & G_LOG_LEVEL_DEBUG) == G_LOG_LEVEL_DEBUG)
925 level = GAIM_DEBUG_MISC;
926 else
927 {
928 gaim_debug_warning("gtkdebug",
929 "Unknown glib logging level in %d\n", flags);
930
931 level = GAIM_DEBUG_MISC; /* This will never happen. */
932 }
933
934 if (msg != NULL)
935 new_msg = gaim_utf8_try_convert(msg);
936
937 if (domain != NULL)
938 new_domain = gaim_utf8_try_convert(domain);
939
940 if (new_msg != NULL)
941 {
942 gaim_debug(level, (new_domain != NULL ? new_domain : "g_log"),
943 "%s\n", new_msg);
944
945 g_free(new_msg);
946 }
947
948 g_free(new_domain);
949 }
950
951 #ifdef _WIN32
952 static void
953 gaim_glib_dummy_print_handler(const gchar *string)
954 {
955 }
956 #endif
957
958 void
959 gaim_gtk_debug_init(void)
960 {
961 /* Debug window preferences. */
962 /*
963 * NOTE: This must be set before prefs are loaded, and the callbacks
964 * set after they are loaded, since prefs sets the enabled
965 * preference here and that loads the window, which calls the
966 * configure event, which overrides the width and height! :P
967 */
968
969 gaim_prefs_add_none("/gaim/gtk/debug");
970
971 /* Controls printing to the debug window */
972 gaim_prefs_add_bool("/gaim/gtk/debug/enabled", FALSE);
973 gaim_prefs_add_int("/gaim/gtk/debug/filterlevel", GAIM_DEBUG_ALL);
974 gaim_prefs_add_int("/gaim/gtk/debug/style", GTK_TOOLBAR_BOTH_HORIZ);
975
976 gaim_prefs_add_bool("/gaim/gtk/debug/toolbar", TRUE);
977 gaim_prefs_add_int("/gaim/gtk/debug/width", 450);
978 gaim_prefs_add_int("/gaim/gtk/debug/height", 250);
979
980 #ifdef HAVE_REGEX_H
981 gaim_prefs_add_string("/gaim/gtk/debug/regex", "");
982 gaim_prefs_add_bool("/gaim/gtk/debug/filter", FALSE);
983 gaim_prefs_add_bool("/gaim/gtk/debug/invert", FALSE);
984 gaim_prefs_add_bool("/gaim/gtk/debug/case_insensitive", FALSE);
985 gaim_prefs_add_bool("/gaim/gtk/debug/highlight", FALSE);
986 #endif /* HAVE_REGEX_H */
987
988 gaim_prefs_connect_callback(NULL, "/gaim/gtk/debug/enabled",
989 debug_enabled_cb, NULL);
990
991 #define REGISTER_G_LOG_HANDLER(name) \
992 g_log_set_handler((name), G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL \
993 | G_LOG_FLAG_RECURSION, \
994 gaim_glib_log_handler, NULL)
995
996 /* Register the glib/gtk log handlers. */
997 REGISTER_G_LOG_HANDLER(NULL);
998 REGISTER_G_LOG_HANDLER("Gdk");
999 REGISTER_G_LOG_HANDLER("Gtk");
1000 REGISTER_G_LOG_HANDLER("GdkPixbuf");
1001 REGISTER_G_LOG_HANDLER("GLib");
1002 REGISTER_G_LOG_HANDLER("GModule");
1003 REGISTER_G_LOG_HANDLER("GLib-GObject");
1004 REGISTER_G_LOG_HANDLER("GThread");
1005
1006 #ifdef _WIN32
1007 if (!gaim_debug_is_enabled())
1008 g_set_print_handler(gaim_glib_dummy_print_handler);
1009 #endif
1010 }
1011
1012 void
1013 gaim_gtk_debug_uninit(void)
1014 {
1015 gaim_debug_set_ui_ops(NULL);
1016 }
1017
1018 void
1019 gaim_gtk_debug_window_show(void)
1020 {
1021 if (debug_win == NULL)
1022 debug_win = debug_window_new();
1023
1024 gtk_widget_show(debug_win->window);
1025
1026 gaim_prefs_set_bool("/gaim/gtk/debug/enabled", TRUE);
1027 }
1028
1029 void
1030 gaim_gtk_debug_window_hide(void)
1031 {
1032 if (debug_win != NULL) {
1033 gtk_widget_destroy(debug_win->window);
1034 debug_window_destroy(NULL, NULL, NULL);
1035 }
1036 }
1037
1038 static void
1039 gaim_gtk_debug_print(GaimDebugLevel level, const char *category,
1040 const char *arg_s)
1041 {
1042 #ifdef HAVE_REGEX_H
1043 GtkTreeIter iter;
1044 #endif /* HAVE_REGEX_H */
1045 gboolean timestamps;
1046 gchar *ts_s;
1047 gchar *esc_s, *cat_s, *tmp, *s;
1048
1049 if (!gaim_prefs_get_bool("/gaim/gtk/debug/enabled") ||
1050 (debug_win == NULL))
1051 {
1052 return;
1053 }
1054
1055 timestamps = gaim_prefs_get_bool("/core/debug/timestamps");
1056
1057 /*
1058 * For some reason we only print the timestamp if category is
1059 * not NULL. Why the hell do we do that? --Mark
1060 */
1061 if ((category != NULL) && (timestamps)) {
1062 const char *mdate;
1063
1064 time_t mtime = time(NULL);
1065 mdate = gaim_utf8_strftime("%H:%M:%S", localtime(&mtime));
1066 ts_s = g_strdup_printf("(%s) ", mdate);
1067 } else {
1068 ts_s = g_strdup("");
1069 }
1070
1071 if (category == NULL)
1072 cat_s = g_strdup("");
1073 else
1074 cat_s = g_strdup_printf("<b>%s:</b> ", category);
1075
1076 esc_s = g_markup_escape_text(arg_s, -1);
1077
1078 s = g_strdup_printf("<font color=\"%s\">%s%s%s</font>",
1079 debug_fg_colors[level], ts_s, cat_s, esc_s);
1080
1081 g_free(ts_s);
1082 g_free(cat_s);
1083 g_free(esc_s);
1084
1085 tmp = gaim_utf8_try_convert(s);
1086 g_free(s);
1087 s = tmp;
1088
1089 if (level == GAIM_DEBUG_FATAL) {
1090 tmp = g_strdup_printf("<b>%s</b>", s);
1091 g_free(s);
1092 s = tmp;
1093 }
1094
1095 #ifdef HAVE_REGEX_H
1096 /* add the text to the list store */
1097 gtk_list_store_append(debug_win->store, &iter);
1098 gtk_list_store_set(debug_win->store, &iter, 0, s, 1, level, -1);
1099 #else /* HAVE_REGEX_H */
1100 if(!debug_win->paused && level >= gaim_prefs_get_int("/gaim/gtk/debug/filterlevel"))
1101 gtk_imhtml_append_text(GTK_IMHTML(debug_win->text), s, 0);
1102 #endif /* !HAVE_REGEX_H */
1103
1104 g_free(s);
1105 }
1106
1107 static GaimDebugUiOps ops =
1108 {
1109 gaim_gtk_debug_print,
1110 };
1111
1112 GaimDebugUiOps *
1113 gaim_gtk_debug_get_ui_ops(void)
1114 {
1115 return &ops;
1116 }
1117
1118 void *
1119 gaim_gtk_debug_get_handle() {
1120 static int handle;
1121
1122 return &handle;
1123 }