Mercurial > pidgin.yaz
comparison src/gtkdebug.c @ 11256:bb0d7b719af2
[gaim-migrate @ 13430]
I give you regex filtering in the debug window.
We keep a buffer of all the text, so when unpausing all the messages that were output when paused will be displayed, as well as when you change the filter.
This _should_ be alright on systems that don't have regex.h but I haven't gotten anyone to test it recently, if it's busted, just #ifdef HAVE_REGEX_H it.
committer: Tailor Script <tailor@pidgin.im>
author | Gary Kramlich <grim@reaperworld.com> |
---|---|
date | Sat, 13 Aug 2005 22:09:34 +0000 |
parents | a511b77a368b |
children | ecbbe6d18b0d |
comparison
equal
deleted
inserted
replaced
11255:1b1d63602d77 | 11256:bb0d7b719af2 |
---|---|
34 #include "gtkdialogs.h" | 34 #include "gtkdialogs.h" |
35 #include "gtkimhtml.h" | 35 #include "gtkimhtml.h" |
36 #include "gtkutils.h" | 36 #include "gtkutils.h" |
37 #include "gtkstock.h" | 37 #include "gtkstock.h" |
38 | 38 |
39 #ifdef HAVE_REGEX_H | |
40 # include <regex.h> | |
41 #endif /* HAVE_REGEX_H */ | |
42 | |
39 typedef struct | 43 typedef struct |
40 { | 44 { |
41 GtkWidget *window; | 45 GtkWidget *window; |
42 GtkWidget *text; | 46 GtkWidget *text; |
43 GtkWidget *find; | 47 |
44 | 48 GtkListStore *store; |
45 /* The category filter tree view. */ | |
46 GtkWidget *treeview; | |
47 | 49 |
48 gboolean timestamps; | 50 gboolean timestamps; |
49 gboolean paused; | 51 gboolean paused; |
50 | 52 |
53 #ifdef HAVE_REGEX_H | |
54 GtkWidget *filter; | |
55 GtkWidget *expression; | |
56 | |
57 gboolean invert; | |
58 gboolean highlight; | |
59 | |
60 guint timer; | |
61 | |
62 regex_t regex; | |
63 #else | |
64 GtkWidget *find; | |
65 #endif /* HAVE_REGEX_H */ | |
51 } DebugWindow; | 66 } DebugWindow; |
52 | 67 |
53 static char debug_fg_colors[][8] = { | 68 static char debug_fg_colors[][8] = { |
54 "#000000", /**< All debug levels. */ | 69 "#000000", /**< All debug levels. */ |
55 "#666666", /**< Misc. */ | 70 "#666666", /**< Misc. */ |
59 "#FF0000", /**< Fatal errors. */ | 74 "#FF0000", /**< Fatal errors. */ |
60 }; | 75 }; |
61 | 76 |
62 static DebugWindow *debug_win = NULL; | 77 static DebugWindow *debug_win = NULL; |
63 | 78 |
64 static GHashTable *debug_categories = NULL; | 79 #ifdef HAVE_REGEX_H |
65 static gboolean filter_enabled = FALSE; | 80 static void regex_filter_all(DebugWindow *win); |
66 | 81 static void regex_show_all(DebugWindow *win); |
82 #endif /* HAVE_REGEX_H */ | |
83 | |
84 static gint | |
85 debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused) | |
86 { | |
87 gaim_prefs_disconnect_by_handle(gaim_gtk_debug_get_handle()); | |
88 | |
89 #ifdef HAVE_REGEX_H | |
90 if(debug_win->timer != 0) { | |
91 const gchar *text; | |
92 | |
93 g_source_remove(debug_win->timer); | |
94 | |
95 text = gtk_entry_get_text(GTK_ENTRY(debug_win->expression)); | |
96 gaim_prefs_set_string("/gaim/gtk/debug/regex", text); | |
97 } | |
98 | |
99 regfree(&debug_win->regex); | |
100 #endif | |
101 | |
102 /* If the "Save Log" dialog is open then close it */ | |
103 gaim_request_close_with_handle(debug_win); | |
104 | |
105 g_free(debug_win); | |
106 debug_win = NULL; | |
107 | |
108 gaim_prefs_set_bool("/gaim/gtk/debug/enabled", FALSE); | |
109 | |
110 return FALSE; | |
111 } | |
112 | |
113 static gboolean | |
114 configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win) | |
115 { | |
116 if (GTK_WIDGET_VISIBLE(w)) { | |
117 gaim_prefs_set_int("/gaim/gtk/debug/width", event->width); | |
118 gaim_prefs_set_int("/gaim/gtk/debug/height", event->height); | |
119 } | |
120 | |
121 return FALSE; | |
122 } | |
123 | |
124 #ifndef HAVE_REGEX_H | |
67 struct _find { | 125 struct _find { |
68 DebugWindow *window; | 126 DebugWindow *window; |
69 GtkWidget *entry; | 127 GtkWidget *entry; |
70 }; | 128 }; |
71 | 129 |
72 static gint | |
73 debug_window_destroy(GtkWidget *w, GdkEvent *event, void *unused) | |
74 { | |
75 gaim_prefs_disconnect_by_handle(gaim_gtk_debug_get_handle()); | |
76 | |
77 /* If the "Save Log" dialog is open then close it */ | |
78 gaim_request_close_with_handle(debug_win); | |
79 | |
80 g_free(debug_win); | |
81 debug_win = NULL; | |
82 | |
83 gaim_prefs_set_bool("/gaim/gtk/debug/enabled", FALSE); | |
84 | |
85 return FALSE; | |
86 } | |
87 | |
88 static gboolean | |
89 configure_cb(GtkWidget *w, GdkEventConfigure *event, DebugWindow *win) | |
90 { | |
91 if (GTK_WIDGET_VISIBLE(w)) { | |
92 gaim_prefs_set_int("/gaim/gtk/debug/width", event->width); | |
93 gaim_prefs_set_int("/gaim/gtk/debug/height", event->height); | |
94 } | |
95 | |
96 return FALSE; | |
97 } | |
98 | |
99 static void | 130 static void |
100 do_find_cb(GtkWidget *widget, gint response, struct _find *f) | 131 do_find_cb(GtkWidget *widget, gint response, struct _find *f) |
101 { | 132 { |
102 switch (response) { | 133 switch (response) { |
103 case GTK_RESPONSE_OK: | 134 case GTK_RESPONSE_OK: |
104 gtk_imhtml_search_find(GTK_IMHTML(f->window->text), | 135 gtk_imhtml_search_find(GTK_IMHTML(f->window->text), |
105 gtk_entry_get_text(GTK_ENTRY(f->entry))); | 136 gtk_entry_get_text(GTK_ENTRY(f->entry))); |
106 break; | 137 break; |
107 | 138 |
108 case GTK_RESPONSE_DELETE_EVENT: | 139 case GTK_RESPONSE_DELETE_EVENT: |
109 case GTK_RESPONSE_CLOSE: | 140 case GTK_RESPONSE_CLOSE: |
169 gtk_box_pack_start(GTK_BOX(hbox), f->entry, FALSE, FALSE, 0); | 200 gtk_box_pack_start(GTK_BOX(hbox), f->entry, FALSE, FALSE, 0); |
170 | 201 |
171 gtk_widget_show_all(win->find); | 202 gtk_widget_show_all(win->find); |
172 gtk_widget_grab_focus(f->entry); | 203 gtk_widget_grab_focus(f->entry); |
173 } | 204 } |
205 #endif /* HAVE_REGEX_H */ | |
174 | 206 |
175 static void | 207 static void |
176 save_writefile_cb(void *user_data, const char *filename) | 208 save_writefile_cb(void *user_data, const char *filename) |
177 { | 209 { |
178 DebugWindow *win = (DebugWindow *)user_data; | 210 DebugWindow *win = (DebugWindow *)user_data; |
201 | 233 |
202 static void | 234 static void |
203 clear_cb(GtkWidget *w, DebugWindow *win) | 235 clear_cb(GtkWidget *w, DebugWindow *win) |
204 { | 236 { |
205 gtk_imhtml_clear(GTK_IMHTML(win->text)); | 237 gtk_imhtml_clear(GTK_IMHTML(win->text)); |
238 | |
239 #ifdef HAVE_REGEX_H | |
240 gtk_list_store_clear(win->store); | |
241 #endif /* HAVE_REGEX_H */ | |
206 } | 242 } |
207 | 243 |
208 static void | 244 static void |
209 pause_cb(GtkWidget *w, DebugWindow *win) | 245 pause_cb(GtkWidget *w, DebugWindow *win) |
210 { | 246 { |
211 win->paused = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); | 247 win->paused = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); |
248 | |
249 #ifdef HAVE_REGEX_H | |
250 if(!win->paused) { | |
251 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) | |
252 regex_filter_all(win); | |
253 else | |
254 regex_show_all(win); | |
255 } | |
256 #endif /* HAVE_REGEX_H */ | |
212 } | 257 } |
213 | 258 |
214 static void | 259 static void |
215 timestamps_cb(GtkWidget *w, DebugWindow *win) | 260 timestamps_cb(GtkWidget *w, DebugWindow *win) |
216 { | 261 { |
224 gpointer data) | 269 gpointer data) |
225 { | 270 { |
226 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data), GPOINTER_TO_INT(value)); | 271 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data), GPOINTER_TO_INT(value)); |
227 } | 272 } |
228 | 273 |
229 static void | 274 /****************************************************************************** |
230 filter_cb(GtkToggleButton *button, DebugWindow *win) | 275 * regex stuff |
231 { | 276 *****************************************************************************/ |
232 if (gtk_toggle_button_get_active(button)) { | 277 #ifdef HAVE_REGEX_H |
233 filter_enabled = TRUE; | 278 static void |
279 regex_clear_color(GtkWidget *w) { | |
280 gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL); | |
281 } | |
282 | |
283 static void | |
284 regex_change_color(GtkWidget *w, guint16 r, guint16 g, guint16 b) { | |
285 GdkColor color; | |
286 | |
287 color.red = r; | |
288 color.green = g; | |
289 color.blue = b; | |
290 | |
291 gtk_widget_modify_base(w, GTK_STATE_NORMAL, &color); | |
292 } | |
293 | |
294 static void | |
295 regex_highlight_clear(DebugWindow *win) { | |
296 GtkIMHtml *imhtml = GTK_IMHTML(win->text); | |
297 GtkTextIter s, e; | |
298 | |
299 gtk_text_buffer_get_start_iter(imhtml->text_buffer, &s); | |
300 gtk_text_buffer_get_end_iter(imhtml->text_buffer, &e); | |
301 gtk_text_buffer_remove_tag_by_name(imhtml->text_buffer, "regex", &s, &e); | |
302 } | |
303 | |
304 static void | |
305 regex_match(DebugWindow *win, const gchar *text) { | |
306 GtkIMHtml *imhtml = GTK_IMHTML(win->text); | |
307 regmatch_t matches[4]; /* adjust if necessary */ | |
308 size_t n_matches = sizeof(matches) / sizeof(matches[0]); | |
309 gchar *plaintext; | |
310 gint inverted; | |
311 | |
312 if(!text) | |
313 return; | |
314 | |
315 inverted = (win->invert) ? REG_NOMATCH : 0; | |
316 | |
317 /* I don't like having to do this, but we need it for highlighting. Plus | |
318 * it makes the ^ and $ operators work :) | |
319 */ | |
320 plaintext = gaim_markup_strip_html(text); | |
321 | |
322 /* we do a first pass to see if it matches at all. If it does we append | |
323 * it, and work out the offsets to highlight. | |
324 */ | |
325 if(regexec(&win->regex, plaintext, n_matches, matches, 0) == inverted) { | |
326 GtkTextIter ins; | |
327 gchar *p = plaintext; | |
328 gint i, offset = 0; | |
329 | |
330 gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &ins, | |
331 gtk_text_buffer_get_insert(imhtml->text_buffer)); | |
332 i = gtk_text_iter_get_offset(&ins); | |
333 | |
334 gtk_imhtml_append_text(imhtml, text, 0); | |
335 | |
336 if(!win->highlight) { | |
337 g_free(plaintext); | |
338 return; | |
339 } | |
340 | |
341 /* we use a do-while to highlight the first match, and then continue | |
342 * if necessary... | |
343 */ | |
344 do { | |
345 gint m; | |
346 | |
347 for(m = 0; m < n_matches; m++) { | |
348 GtkTextIter ms, me; | |
349 | |
350 if(matches[m].rm_eo == -1) | |
351 break; | |
352 | |
353 i += offset; | |
354 | |
355 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &ms, | |
356 i + matches[m].rm_so); | |
357 gtk_text_buffer_get_iter_at_offset(imhtml->text_buffer, &me, | |
358 i + matches[m].rm_eo); | |
359 gtk_text_buffer_apply_tag_by_name(imhtml->text_buffer, "regex", | |
360 &ms, &me); | |
361 offset = matches[m].rm_eo; | |
362 } | |
363 | |
364 p += offset; | |
365 } while(regexec(&win->regex, p, n_matches, matches, REG_NOTBOL) == inverted); | |
366 } | |
367 | |
368 g_free(plaintext); | |
369 } | |
370 | |
371 static gboolean | |
372 regex_filter_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter, | |
373 gpointer data) | |
374 { | |
375 DebugWindow *win = (DebugWindow *)data; | |
376 gchar *text; | |
377 | |
378 gtk_tree_model_get(m, iter, 0, &text, -1); | |
379 | |
380 regex_match(win, text); | |
381 | |
382 g_free(text); | |
383 | |
384 return FALSE; | |
385 } | |
386 | |
387 static void | |
388 regex_filter_all(DebugWindow *win) { | |
389 gtk_imhtml_clear(GTK_IMHTML(win->text)); | |
390 | |
391 if(win->highlight) | |
392 regex_highlight_clear(win); | |
393 | |
394 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_filter_all_cb, | |
395 win); | |
396 } | |
397 | |
398 static gboolean | |
399 regex_show_all_cb(GtkTreeModel *m, GtkTreePath *p, GtkTreeIter *iter, | |
400 gpointer data) | |
401 { | |
402 DebugWindow *win = (DebugWindow *)data; | |
403 gchar *text; | |
404 | |
405 gtk_tree_model_get(m, iter, 0, &text, -1); | |
406 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0); | |
407 g_free(text); | |
408 | |
409 return FALSE; | |
410 } | |
411 | |
412 static void | |
413 regex_show_all(DebugWindow *win) { | |
414 gtk_imhtml_clear(GTK_IMHTML(win->text)); | |
415 | |
416 if(win->highlight) | |
417 regex_highlight_clear(win); | |
418 | |
419 gtk_tree_model_foreach(GTK_TREE_MODEL(win->store), regex_show_all_cb, | |
420 win); | |
421 } | |
422 | |
423 static void | |
424 regex_compile(DebugWindow *win) { | |
425 const gchar *text; | |
426 | |
427 text = gtk_entry_get_text(GTK_ENTRY(win->expression)); | |
428 | |
429 if(text == NULL || *text == '\0') { | |
430 regex_clear_color(win->expression); | |
431 gtk_widget_set_sensitive(win->filter, FALSE); | |
432 return; | |
433 } | |
434 | |
435 regfree(&win->regex); | |
436 | |
437 if(regcomp(&win->regex, text, REG_EXTENDED | REG_ICASE) != 0) { | |
438 /* failed to compile */ | |
439 regex_change_color(win->expression, 0xFFFF, 0xAFFF, 0xAFFF); | |
440 gtk_widget_set_sensitive(win->filter, FALSE); | |
234 } else { | 441 } else { |
235 filter_enabled = FALSE; | 442 /* compiled successfully */ |
236 } | 443 regex_change_color(win->expression, 0xAFFF, 0xFFFF, 0xAFFF); |
237 } | 444 gtk_widget_set_sensitive(win->filter, TRUE); |
238 | 445 } |
239 static void | 446 |
240 debug_liststore_append(gpointer key, gpointer value, gpointer user_data) | 447 /* we check if the filter is on in case it was only of the options that |
241 { | 448 * got changed, and not the expression. |
242 GtkTreeIter iter; | 449 */ |
243 GtkListStore **liststore = (GtkListStore **)user_data; | 450 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) |
244 | 451 regex_filter_all(win); |
245 gtk_list_store_append(*liststore, &iter); | 452 } |
246 gtk_list_store_set(*liststore, &iter, 0, key, -1); | 453 |
247 } | 454 static void |
455 regex_pref_filter_cb(const gchar *name, GaimPrefType type, | |
456 gpointer val, gpointer data) | |
457 { | |
458 DebugWindow *win = (DebugWindow *)data; | |
459 gboolean active = GPOINTER_TO_INT(val), current; | |
460 | |
461 if(!win || !win->window) | |
462 return; | |
463 | |
464 current = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter)); | |
465 if(active != current) | |
466 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), active); | |
467 } | |
468 | |
469 static void | |
470 regex_pref_expression_cb(const gchar *name, GaimPrefType type, | |
471 gpointer val, gpointer data) | |
472 { | |
473 DebugWindow *win = (DebugWindow *)data; | |
474 const gchar *exp = (const gchar *)val; | |
475 | |
476 gtk_entry_set_text(GTK_ENTRY(win->expression), exp); | |
477 } | |
478 | |
479 static void | |
480 regex_pref_invert_cb(const gchar *name, GaimPrefType type, | |
481 gpointer val, gpointer data) | |
482 { | |
483 DebugWindow *win = (DebugWindow *)data; | |
484 gboolean active = GPOINTER_TO_INT(val); | |
485 | |
486 win->invert = active; | |
487 | |
488 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) | |
489 regex_filter_all(win); | |
490 } | |
491 | |
492 static void | |
493 regex_pref_highlight_cb(const gchar *name, GaimPrefType type, | |
494 gpointer val, gpointer data) | |
495 { | |
496 DebugWindow *win = (DebugWindow *)data; | |
497 gboolean active = GPOINTER_TO_INT(val); | |
498 | |
499 win->highlight = active; | |
500 | |
501 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) | |
502 regex_filter_all(win); | |
503 } | |
504 | |
505 static void | |
506 regex_row_changed_cb(GtkTreeModel *model, GtkTreePath *path, | |
507 GtkTreeIter *iter, DebugWindow *win) | |
508 { | |
509 gchar *text; | |
510 | |
511 if(!win || !win->window) | |
512 return; | |
513 | |
514 /* If the debug window is paused, we just return since it's in the store. | |
515 * We don't call regex_match because it doesn't make sense to check the | |
516 * string if it's paused. When we unpause we clear the imhtml and | |
517 * reiterate over the store to handle matches that were outputted when | |
518 * we were paused. | |
519 */ | |
520 if(win->paused) | |
521 return; | |
522 | |
523 gtk_tree_model_get(model, iter, 0, &text, -1); | |
524 | |
525 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) { | |
526 regex_match(win, text); | |
527 } else { | |
528 gtk_imhtml_append_text(GTK_IMHTML(win->text), text, 0); | |
529 } | |
530 | |
531 g_free(text); | |
532 } | |
533 | |
534 static gboolean | |
535 regex_timer_cb(DebugWindow *win) { | |
536 const gchar *text; | |
537 | |
538 text = gtk_entry_get_text(GTK_ENTRY(win->expression)); | |
539 gaim_prefs_set_string("/gaim/gtk/debug/regex", text); | |
540 | |
541 win->timer = 0; | |
542 | |
543 return FALSE; | |
544 } | |
545 | |
546 static void | |
547 regex_changed_cb(GtkWidget *w, DebugWindow *win) { | |
548 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) { | |
549 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), | |
550 FALSE); | |
551 } | |
552 | |
553 if(win->timer == 0) | |
554 win->timer = gaim_timeout_add(5000, (GSourceFunc)regex_timer_cb, win); | |
555 | |
556 regex_compile(win); | |
557 } | |
558 | |
559 static void | |
560 regex_menu_cb(GtkWidget *item, const gchar *pref) { | |
561 gboolean active; | |
562 | |
563 active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item)); | |
564 | |
565 gaim_prefs_set_bool(pref, active); | |
566 } | |
567 | |
568 static void | |
569 regex_popup_cb(GtkEntry *entry, GtkWidget *menu, DebugWindow *win) { | |
570 gaim_separator(menu); | |
571 gaim_new_check_item(menu, _("Invert"), | |
572 G_CALLBACK(regex_menu_cb), | |
573 "/gaim/gtk/debug/invert", win->invert); | |
574 gaim_new_check_item(menu, _("Highlight matches"), | |
575 G_CALLBACK(regex_menu_cb), | |
576 "/gaim/gtk/debug/highlight", win->highlight); | |
577 } | |
578 | |
579 static void | |
580 regex_filter_toggled_cb(GtkToggleButton *button, DebugWindow *win) { | |
581 gboolean active; | |
582 | |
583 active = gtk_toggle_button_get_active(button); | |
584 | |
585 gaim_prefs_set_bool("/gaim/gtk/debug/filter", active); | |
586 | |
587 if(!GTK_IS_IMHTML(win->text)) | |
588 return; | |
589 | |
590 if(active) | |
591 regex_filter_all(win); | |
592 else | |
593 regex_show_all(win); | |
594 } | |
595 | |
596 #endif /* HAVE_REGEX_H */ | |
248 | 597 |
249 static DebugWindow * | 598 static DebugWindow * |
250 debug_window_new(void) | 599 debug_window_new(void) |
251 { | 600 { |
252 DebugWindow *win; | 601 DebugWindow *win; |
253 GtkWidget *vbox; | 602 GtkWidget *vbox; |
254 GtkWidget *toolbar; | 603 GtkWidget *toolbar; |
255 GtkWidget *frame; | 604 GtkWidget *frame; |
256 GtkWidget *button; | 605 GtkWidget *button; |
257 GtkWidget *image; | 606 GtkWidget *image; |
258 GtkListStore *liststore = NULL; | 607 gint width, height; |
259 GtkCellRenderer *renderer = NULL; | 608 void *handle; |
260 GtkTreeSelection *selection = NULL; | |
261 GtkTreeViewColumn *column = NULL; | |
262 int width, height; | |
263 | 609 |
264 win = g_new0(DebugWindow, 1); | 610 win = g_new0(DebugWindow, 1); |
265 | 611 |
266 width = gaim_prefs_get_int("/gaim/gtk/debug/width"); | 612 width = gaim_prefs_get_int("/gaim/gtk/debug/width"); |
267 height = gaim_prefs_get_int("/gaim/gtk/debug/height"); | 613 height = gaim_prefs_get_int("/gaim/gtk/debug/height"); |
268 | 614 |
269 GAIM_DIALOG(win->window); | 615 GAIM_DIALOG(win->window); |
270 gaim_debug_info("gtkdebug", "Setting dimensions to %d, %d\n", | 616 gaim_debug_info("gtkdebug", "Setting dimensions to %d, %d\n", |
271 width, height); | 617 width, height); |
272 | 618 |
273 gtk_window_set_default_size(GTK_WINDOW(win->window), width, height); | 619 gtk_window_set_default_size(GTK_WINDOW(win->window), width, height); |
274 gtk_window_set_role(GTK_WINDOW(win->window), "debug"); | 620 gtk_window_set_role(GTK_WINDOW(win->window), "debug"); |
275 gtk_window_set_title(GTK_WINDOW(win->window), _("Debug Window")); | 621 gtk_window_set_title(GTK_WINDOW(win->window), _("Debug Window")); |
276 | 622 |
277 g_signal_connect(G_OBJECT(win->window), "delete_event", | 623 g_signal_connect(G_OBJECT(win->window), "delete_event", |
278 G_CALLBACK(debug_window_destroy), NULL); | 624 G_CALLBACK(debug_window_destroy), NULL); |
279 g_signal_connect(G_OBJECT(win->window), "configure_event", | 625 g_signal_connect(G_OBJECT(win->window), "configure_event", |
280 G_CALLBACK(configure_cb), win); | 626 G_CALLBACK(configure_cb), win); |
281 | 627 |
628 handle = gaim_gtk_debug_get_handle(); | |
629 | |
630 #ifdef HAVE_REGEX_H | |
631 /* the list store for all the messages */ | |
632 win->store = gtk_list_store_new(1, G_TYPE_STRING); | |
633 | |
634 /* row-changed gets called when we do gtk_list_store_set, and row-inserted | |
635 * gets called with gtk_list_store_append, which is a | |
636 * completely empty row. So we just ignore row-inserted, and deal with row | |
637 * changed. -Gary | |
638 */ | |
639 g_signal_connect(G_OBJECT(win->store), "row-changed", | |
640 G_CALLBACK(regex_row_changed_cb), win); | |
641 | |
642 #endif /* HAVE_REGEX_H */ | |
643 | |
282 /* Setup the vbox */ | 644 /* Setup the vbox */ |
283 vbox = gtk_vbox_new(FALSE, 0); | 645 vbox = gtk_vbox_new(FALSE, 0); |
284 gtk_container_add(GTK_CONTAINER(win->window), vbox); | 646 gtk_container_add(GTK_CONTAINER(win->window), vbox); |
285 | 647 |
286 if (gaim_prefs_get_bool("/gaim/gtk/debug/toolbar")) { | 648 if (gaim_prefs_get_bool("/gaim/gtk/debug/toolbar")) { |
287 /* Setup our top button bar thingie. */ | 649 /* Setup our top button bar thingie. */ |
288 toolbar = gtk_toolbar_new(); | 650 toolbar = gtk_toolbar_new(); |
651 gtk_toolbar_set_tooltips(GTK_TOOLBAR(toolbar), TRUE); | |
652 | |
289 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), | 653 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), |
290 GTK_TOOLBAR_BOTH_HORIZ); | 654 GTK_TOOLBAR_BOTH_HORIZ); |
291 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), | 655 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), |
292 GTK_ICON_SIZE_SMALL_TOOLBAR); | 656 GTK_ICON_SIZE_SMALL_TOOLBAR); |
293 | 657 |
294 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); | 658 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); |
295 | 659 |
660 #ifndef HAVE_REGEX_H | |
296 /* Find button */ | 661 /* Find button */ |
297 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_FIND, | 662 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_FIND, |
298 NULL, NULL, G_CALLBACK(find_cb), | 663 _("Find"), NULL, G_CALLBACK(find_cb), |
299 win, -1); | 664 win, -1); |
665 #endif /* HAVE_REGEX_H */ | |
300 | 666 |
301 /* Save */ | 667 /* Save */ |
302 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_SAVE, | 668 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_SAVE, |
303 NULL, NULL, G_CALLBACK(save_cb), | 669 _("Save"), NULL, G_CALLBACK(save_cb), |
304 win, -1); | 670 win, -1); |
305 | 671 |
306 /* Clear button */ | 672 /* Clear button */ |
307 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_CLEAR, | 673 gtk_toolbar_insert_stock(GTK_TOOLBAR(toolbar), GTK_STOCK_CLEAR, |
308 NULL, NULL, G_CALLBACK(clear_cb), | 674 _("Clear"), NULL, G_CALLBACK(clear_cb), |
309 win, -1); | 675 win, -1); |
310 | 676 |
311 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1); | 677 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1); |
312 | 678 |
313 /* Pause */ | 679 /* Pause */ |
314 image = gtk_image_new_from_stock(GAIM_STOCK_PAUSE, GTK_ICON_SIZE_MENU); | 680 image = gtk_image_new_from_stock(GAIM_STOCK_PAUSE, GTK_ICON_SIZE_MENU); |
315 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), | 681 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), |
316 GTK_TOOLBAR_CHILD_TOGGLEBUTTON, | 682 GTK_TOOLBAR_CHILD_TOGGLEBUTTON, |
317 NULL, _("Pause"), NULL, | 683 NULL, _("Pause"), _("Pause"), |
318 NULL, image, | 684 NULL, image, |
319 G_CALLBACK(pause_cb), win); | 685 G_CALLBACK(pause_cb), win); |
320 | 686 |
321 /* Timestamps */ | 687 /* Timestamps */ |
322 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), | 688 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), |
323 GTK_TOOLBAR_CHILD_TOGGLEBUTTON, | 689 GTK_TOOLBAR_CHILD_TOGGLEBUTTON, |
324 NULL, _("Timestamps"), | 690 NULL, _("Timestamps"), |
325 NULL, NULL, NULL, | 691 _("Timestamps"), NULL, NULL, |
326 G_CALLBACK(timestamps_cb), | 692 G_CALLBACK(timestamps_cb), |
327 win); | 693 win); |
328 | 694 |
329 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), | 695 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), |
330 gaim_prefs_get_bool("/core/debug/timestamps")); | 696 gaim_prefs_get_bool("/core/debug/timestamps")); |
331 | 697 |
332 gaim_prefs_connect_callback(gaim_gtk_debug_get_handle(), | 698 gaim_prefs_connect_callback(handle, "/core/debug/timestamps", |
333 "/core/debug/timestamps", | |
334 timestamps_pref_cb, button); | 699 timestamps_pref_cb, button); |
335 | 700 |
336 button = gtk_check_button_new_with_label(_("Filter")); | 701 #ifdef HAVE_REGEX_H |
337 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(filter_cb), win); | 702 /* regex stuff */ |
338 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), | 703 gtk_toolbar_insert_space(GTK_TOOLBAR(toolbar), -1); |
339 GTK_TOOLBAR_CHILD_WIDGET, | 704 |
340 button, NULL, NULL, NULL, | 705 /* regex toggle button */ |
341 NULL, NULL, NULL); | 706 win->filter = |
342 | 707 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), |
343 button = gtk_scrolled_window_new(NULL, NULL); | 708 GTK_TOOLBAR_CHILD_TOGGLEBUTTON, |
344 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(button), | 709 NULL, _("Filter"), _("Filter"), |
345 GTK_POLICY_NEVER, | 710 NULL, NULL, |
346 GTK_POLICY_AUTOMATIC); | 711 G_CALLBACK(regex_filter_toggled_cb), |
347 | 712 win); |
348 liststore = gtk_list_store_new(1, G_TYPE_STRING); | 713 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), |
349 win->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore)); | 714 gaim_prefs_get_bool("/gaim/gtk/debug/filter")); |
350 renderer = gtk_cell_renderer_text_new(); | 715 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/filter", |
351 column = gtk_tree_view_column_new_with_attributes(_("Filter"), renderer, "text", 0, NULL); | 716 regex_pref_filter_cb, win); |
352 | 717 |
353 gtk_tree_view_append_column(GTK_TREE_VIEW(win->treeview), column); | 718 /* regex entry */ |
354 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(win->treeview), FALSE); | 719 win->expression = gtk_entry_new(); |
355 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(liststore), 0, GTK_SORT_ASCENDING); | 720 gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), |
356 | 721 GTK_TOOLBAR_CHILD_WIDGET, win->expression, |
357 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(win->treeview)); | 722 NULL, _("Right click for more options."), |
358 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); | 723 NULL, NULL, NULL, NULL); |
359 | 724 /* this needs to be before the text is set from the pref if we want it |
360 g_hash_table_foreach(debug_categories, (GHFunc)debug_liststore_append, &liststore); | 725 * to colorize a stored expression. |
361 | 726 */ |
362 gtk_container_add(GTK_CONTAINER(button), win->treeview); | 727 g_signal_connect(G_OBJECT(win->expression), "changed", |
363 button = gtk_toolbar_append_element(GTK_TOOLBAR(toolbar), | 728 G_CALLBACK(regex_changed_cb), win); |
364 GTK_TOOLBAR_CHILD_WIDGET, | 729 gtk_entry_set_text(GTK_ENTRY(win->expression), |
365 button, NULL, NULL, | 730 gaim_prefs_get_string("/gaim/gtk/debug/regex")); |
366 NULL, NULL, NULL, NULL); | 731 g_signal_connect(G_OBJECT(win->expression), "populate-popup", |
732 G_CALLBACK(regex_popup_cb), win); | |
733 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/regex", | |
734 regex_pref_expression_cb, win); | |
735 | |
736 /* connect the rest of our pref callbacks */ | |
737 win->invert = gaim_prefs_get_bool("/gaim/gtk/debug/invert"); | |
738 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/invert", | |
739 regex_pref_invert_cb, win); | |
740 | |
741 win->highlight = gaim_prefs_get_bool("/gaim/gtk/debug/highlight"); | |
742 gaim_prefs_connect_callback(handle, "/gaim/gtk/debug/highlight", | |
743 regex_pref_highlight_cb, win); | |
744 | |
745 #endif /* HAVE_REGEX_H */ | |
367 } | 746 } |
368 | 747 |
369 /* Add the gtkimhtml */ | 748 /* Add the gtkimhtml */ |
370 frame = gaim_gtk_create_imhtml(FALSE, &win->text, NULL); | 749 frame = gaim_gtk_create_imhtml(FALSE, &win->text, NULL); |
371 gtk_imhtml_set_format_functions(GTK_IMHTML(win->text), | 750 gtk_imhtml_set_format_functions(GTK_IMHTML(win->text), |
372 GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY ^ GTK_IMHTML_IMAGE); | 751 GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY ^ GTK_IMHTML_IMAGE); |
373 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); | 752 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); |
374 gtk_widget_show(frame); | 753 gtk_widget_show(frame); |
754 | |
755 #ifdef HAVE_REGEX_H | |
756 /* add the tag for regex highlighting */ | |
757 gtk_text_buffer_create_tag(GTK_IMHTML(win->text)->text_buffer, "regex", | |
758 "background", "#FFAFAF", | |
759 "weight", "bold", | |
760 NULL); | |
761 #endif /* HAVE_REGEX_H */ | |
375 | 762 |
376 gtk_widget_show_all(win->window); | 763 gtk_widget_show_all(win->window); |
377 | 764 |
378 return win; | 765 return win; |
379 } | 766 } |
442 #endif | 829 #endif |
443 | 830 |
444 void | 831 void |
445 gaim_gtk_debug_init(void) | 832 gaim_gtk_debug_init(void) |
446 { | 833 { |
447 gaim_debug_register_category("gtkdebug"); | |
448 | |
449 /* Debug window preferences. */ | 834 /* Debug window preferences. */ |
450 /* | 835 /* |
451 * NOTE: This must be set before prefs are loaded, and the callbacks | 836 * NOTE: This must be set before prefs are loaded, and the callbacks |
452 * set after they are loaded, since prefs sets the enabled | 837 * set after they are loaded, since prefs sets the enabled |
453 * preference here and that loads the window, which calls the | 838 * preference here and that loads the window, which calls the |
460 gaim_prefs_add_bool("/gaim/gtk/debug/enabled", FALSE); | 845 gaim_prefs_add_bool("/gaim/gtk/debug/enabled", FALSE); |
461 | 846 |
462 gaim_prefs_add_bool("/gaim/gtk/debug/toolbar", TRUE); | 847 gaim_prefs_add_bool("/gaim/gtk/debug/toolbar", TRUE); |
463 gaim_prefs_add_int("/gaim/gtk/debug/width", 450); | 848 gaim_prefs_add_int("/gaim/gtk/debug/width", 450); |
464 gaim_prefs_add_int("/gaim/gtk/debug/height", 250); | 849 gaim_prefs_add_int("/gaim/gtk/debug/height", 250); |
850 | |
851 #ifdef HAVE_REGEX_H | |
852 gaim_prefs_add_string("/gaim/gtk/debug/regex", ""); | |
853 gaim_prefs_add_bool("/gaim/gtk/debug/filter", FALSE); | |
854 gaim_prefs_add_bool("/gaim/gtk/debug/invert", FALSE); | |
855 gaim_prefs_add_bool("/gaim/gtk/debug/case_insensitive", FALSE); | |
856 gaim_prefs_add_bool("/gaim/gtk/debug/highlight", FALSE); | |
857 #endif /* HAVE_REGEX_H */ | |
465 | 858 |
466 gaim_prefs_connect_callback(NULL, "/gaim/gtk/debug/enabled", | 859 gaim_prefs_connect_callback(NULL, "/gaim/gtk/debug/enabled", |
467 debug_enabled_cb, NULL); | 860 debug_enabled_cb, NULL); |
468 | 861 |
469 #define REGISTER_G_LOG_HANDLER(name) \ | 862 #define REGISTER_G_LOG_HANDLER(name) \ |
488 } | 881 } |
489 | 882 |
490 void | 883 void |
491 gaim_gtk_debug_uninit(void) | 884 gaim_gtk_debug_uninit(void) |
492 { | 885 { |
493 gaim_debug_unregister_category("gtkdebug"); | |
494 | |
495 gaim_debug_set_ui_ops(NULL); | 886 gaim_debug_set_ui_ops(NULL); |
496 } | 887 } |
497 | 888 |
498 void | 889 void |
499 gaim_gtk_debug_window_show(void) | 890 gaim_gtk_debug_window_show(void) |
514 debug_window_destroy(NULL, NULL, NULL); | 905 debug_window_destroy(NULL, NULL, NULL); |
515 } | 906 } |
516 } | 907 } |
517 | 908 |
518 static void | 909 static void |
519 create_debug_selected_categories(GtkTreeModel *model, GtkTreePath *path, | |
520 GtkTreeIter *iter, gpointer data) | |
521 { | |
522 GHashTable **hashtable = (GHashTable **)data; | |
523 char *text = NULL; | |
524 | |
525 gtk_tree_model_get(model, iter, 0, &text, -1); | |
526 | |
527 g_hash_table_insert(*hashtable, text, NULL); | |
528 } | |
529 | |
530 static gboolean | |
531 debug_is_filtered_out(const char *category) | |
532 { | |
533 GtkTreeSelection *selection = NULL; | |
534 GHashTable *hashtable = NULL; | |
535 gboolean found = FALSE; | |
536 | |
537 if (category == NULL) | |
538 return FALSE; | |
539 | |
540 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(debug_win->treeview)); | |
541 hashtable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, | |
542 NULL); | |
543 | |
544 gtk_tree_selection_selected_foreach(selection, | |
545 create_debug_selected_categories, | |
546 &hashtable); | |
547 | |
548 if (filter_enabled) { | |
549 if (g_hash_table_lookup_extended(hashtable, category, NULL, NULL)) | |
550 found = FALSE; | |
551 else | |
552 found = TRUE; | |
553 } | |
554 | |
555 g_hash_table_destroy(hashtable); | |
556 return found; | |
557 } | |
558 | |
559 static void | |
560 gaim_gtk_debug_print(GaimDebugLevel level, const char *category, | 910 gaim_gtk_debug_print(GaimDebugLevel level, const char *category, |
561 const char *format, va_list args) | 911 const char *format, va_list args) |
562 { | 912 { |
913 GtkTreeIter iter; | |
563 gboolean timestamps; | 914 gboolean timestamps; |
564 gchar *arg_s, *ts_s; | 915 gchar *arg_s, *ts_s; |
565 gchar *esc_s, *cat_s, *tmp, *s; | 916 gchar *esc_s, *cat_s, *tmp, *s; |
566 | 917 |
567 if (!gaim_prefs_get_bool("/gaim/gtk/debug/enabled") || | 918 if (!gaim_prefs_get_bool("/gaim/gtk/debug/enabled") || |
568 (debug_win == NULL) || debug_win->paused || | 919 (debug_win == NULL)) |
569 debug_is_filtered_out(category)) { | 920 { |
570 return; | 921 return; |
571 } | 922 } |
572 | 923 |
573 timestamps = gaim_prefs_get_bool("/core/debug/timestamps"); | 924 timestamps = gaim_prefs_get_bool("/core/debug/timestamps"); |
574 | 925 |
612 tmp = g_strdup_printf("<b>%s</b>", s); | 963 tmp = g_strdup_printf("<b>%s</b>", s); |
613 g_free(s); | 964 g_free(s); |
614 s = tmp; | 965 s = tmp; |
615 } | 966 } |
616 | 967 |
617 gtk_imhtml_append_text(GTK_IMHTML(debug_win->text), s, 0); | 968 #ifdef HAVE_REGEX_H |
969 /* add the text to the list store */ | |
970 gtk_list_store_append(debug_win->store, &iter); | |
971 gtk_list_store_set(debug_win->store, &iter, 0, s, -1); | |
972 #else /* HAVE_REGEX_H */ | |
973 if(!debug_win->paused) | |
974 gtk_imhtml_append_text(GTK_IMHTML(debug_win->text), s, 0); | |
975 #endif /* !HAVE_REGEX_H */ | |
618 | 976 |
619 g_free(s); | 977 g_free(s); |
620 } | 978 } |
621 | 979 |
622 static void | |
623 gaim_gtk_debug_register_category(const char *category) | |
624 { | |
625 /* XXX I'd like to be able to put this creation in _init, but that | |
626 * would require that this be init:ed before anything that wants to | |
627 * register a category, and I'm not sure I can count on this coming | |
628 * first */ | |
629 if (debug_categories == NULL) | |
630 debug_categories = g_hash_table_new_full(g_str_hash, | |
631 g_str_equal, | |
632 g_free, NULL); | |
633 | |
634 if (!g_hash_table_lookup_extended(debug_categories, category, NULL, NULL)) { | |
635 g_hash_table_insert(debug_categories, g_strdup(category), NULL); | |
636 | |
637 if (debug_win != NULL && debug_win->treeview != NULL) { | |
638 GtkTreeModel *model = NULL; | |
639 GtkTreeIter iter; | |
640 | |
641 model = gtk_tree_view_get_model(GTK_TREE_VIEW(debug_win->treeview)); | |
642 | |
643 gtk_list_store_append(GTK_LIST_STORE(model), &iter); | |
644 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, | |
645 category, -1); | |
646 } | |
647 } | |
648 } | |
649 | |
650 static gboolean | |
651 find_and_remove_category(GtkTreeModel *model, GtkTreePath *path, | |
652 GtkTreeIter *iter, gpointer data) | |
653 { | |
654 GValue value = {0}; | |
655 | |
656 gtk_tree_model_get_value(model, iter, 0, &value); | |
657 | |
658 if (strcmp(g_value_get_string(&value), data) == 0) { | |
659 gtk_list_store_remove(GTK_LIST_STORE(model), iter); | |
660 | |
661 return TRUE; | |
662 } | |
663 | |
664 return FALSE; | |
665 } | |
666 | |
667 static void | |
668 gaim_gtk_debug_unregister_category(const char *category) | |
669 { | |
670 GtkTreeModel *model = NULL; | |
671 | |
672 if (debug_win == NULL) | |
673 return; | |
674 | |
675 model = gtk_tree_view_get_model(GTK_TREE_VIEW(debug_win->treeview)); | |
676 | |
677 gtk_tree_model_foreach(model, | |
678 (GtkTreeModelForeachFunc)find_and_remove_category, | |
679 (char *)category); | |
680 | |
681 g_hash_table_remove(debug_categories, category); | |
682 } | |
683 | |
684 static GaimDebugUiOps ops = | 980 static GaimDebugUiOps ops = |
685 { | 981 { |
686 gaim_gtk_debug_print, | 982 gaim_gtk_debug_print, |
687 gaim_gtk_debug_register_category, | |
688 gaim_gtk_debug_unregister_category | |
689 }; | 983 }; |
690 | 984 |
691 GaimDebugUiOps * | 985 GaimDebugUiOps * |
692 gaim_gtk_debug_get_ui_ops(void) | 986 gaim_gtk_debug_get_ui_ops(void) |
693 { | 987 { |