Have the time field in the Graph Analyzis windos use the same time format as used...
[metze/wireshark/wip.git] / gtk / funnel_stat.c
1 /*
2  * funnel_stat.c
3  *
4  * EPAN's funneled GUI mini-API
5  *
6  * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
7  *
8  * $Id$
9  *
10  * Wireshark - Network traffic analyzer
11  * By Gerald Combs <gerald@wireshark.org>
12  * Copyright 1998 Gerald Combs
13  *
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27  */
28
29 /*
30  * Most of the code here has been harvested from other Wireshark gtk modules.
31  * most from prefs_dlg.c and about_dlg.c
32  *
33  * (From original checkin message:
34  * The funneled GUI mini API.
35  * A very reduced set of gui ops (by now just a text window) 
36  * that can be funneled to dissectors (even plugins) via epan.
37  */
38
39 #ifdef HAVE_CONFIG_H
40 # include "config.h"
41 #endif
42
43 #ifdef HAVE_SYS_TYPES_H
44 # include <sys/types.h>
45 #endif
46
47 #include <stdio.h>
48 #include <string.h>
49
50 #include <gtk/gtk.h>
51
52 #include <epan/prefs.h>
53 #include <epan/funnel.h>
54
55 #include "../timestats.h"
56 #include "../simple_dialog.h"
57 #include "../file.h"
58 #include "../stat_menu.h"
59 #include "../progress_dlg.h"
60 #include "../color_filters.h"
61
62 #include "gtk/gui_utils.h"
63 #include "gtk/dlg_utils.h"
64 #include "gtk/tap_param_dlg.h"
65 #include "gtk/font_utils.h"
66 #include "gtk/gui_stat_menu.h"
67 #include "gtk/prefs_dlg.h"
68 #include "gtk/main.h"
69 #include "gtk/webbrowser.h"
70 #include "gtk/gtkglobals.h"
71
72
73 struct _funnel_text_window_t {
74         GtkWidget* win;
75     GtkWidget* txt;
76     GtkWidget* button_hbox;
77     GtkWidget* bt_close;
78     text_win_close_cb_t close_cb;
79     void* close_data;
80         GPtrArray* buttons;
81 };
82
83 struct _funnel_tree_window_t {
84         GtkWidget *win;
85
86 };
87
88 struct _funnel_node_t {
89     void* dummy;
90 };
91
92 static void text_window_cancel_button_cb(GtkWidget *bt _U_, gpointer data) {
93     funnel_text_window_t* tw = data;
94
95     window_destroy(GTK_WIDGET(tw->win));
96     tw->win = NULL;
97
98     if (tw->close_cb)
99         tw->close_cb(tw->close_data);
100 }
101
102 static void unref_text_win_cancel_bt_cb(GtkWidget *bt _U_, gpointer data) {
103     funnel_text_window_t* tw = data;
104     unsigned i;
105
106     window_destroy(GTK_WIDGET(tw->win));
107     tw->win = NULL;
108
109     if (tw->close_cb)
110         tw->close_cb(tw->close_data);
111
112         for (i = 0; i < tw->buttons->len; i++) {
113                 funnel_bt_t* cbd = g_ptr_array_index(tw->buttons,i);
114                 /* XXX a free cb should be passed somehow */
115                 if (cbd->data && cbd->free_data_fcn) cbd->free_data_fcn(cbd->data);
116                 if (cbd->free_fcn) cbd->free_fcn(cbd);
117         }
118         g_ptr_array_free(tw->buttons,TRUE);
119     g_free(tw);
120 }
121
122
123 static gboolean text_window_unref_del_event_cb(GtkWidget *win _U_, GdkEvent *event _U_, gpointer user_data) {
124     funnel_text_window_t* tw = user_data;
125     unsigned i;
126
127     window_destroy(GTK_WIDGET(tw->win));
128     tw->win = NULL;
129
130     if (tw->close_cb)
131         tw->close_cb(tw->close_data);
132
133         for (i = 0; i < tw->buttons->len; i++) {
134                 funnel_bt_t* cbd = g_ptr_array_index(tw->buttons,i);
135                 /* XXX a free cb should be passed somehow */
136                 if (cbd->data && cbd->free_data_fcn) cbd->free_data_fcn(cbd->data);
137                 if (cbd->free_fcn) cbd->free_fcn(cbd);
138         }
139         g_ptr_array_free(tw->buttons,TRUE);
140     g_free(tw);
141
142     return TRUE;
143 }
144
145 static gboolean text_window_delete_event_cb(GtkWidget *win _U_, GdkEvent *event _U_, gpointer user_data)
146 {
147     funnel_text_window_t* tw = user_data;
148
149     window_destroy(GTK_WIDGET(tw->win));
150     tw->win = NULL;
151
152     if (tw->close_cb)
153         tw->close_cb(tw->close_data);
154
155     return TRUE;
156 }
157
158 static funnel_text_window_t* new_text_window(const gchar* title) {
159     funnel_text_window_t* tw = g_malloc(sizeof(funnel_text_window_t));
160         GtkWidget *txt_scrollw, *main_vb, *hbox;
161
162     tw->close_cb = NULL;
163     tw->close_data = NULL;
164     tw->buttons = g_ptr_array_new();
165
166         tw->win = dlg_window_new(title);  /* transient_for top_level */
167         gtk_window_set_destroy_with_parent (GTK_WINDOW(tw->win), TRUE);
168
169     g_signal_connect(tw->win, "delete-event", G_CALLBACK(text_window_delete_event_cb), tw);
170
171     txt_scrollw = scrolled_window_new(NULL, NULL);
172     main_vb = gtk_vbox_new(FALSE, 3);
173         gtk_container_set_border_width(GTK_CONTAINER(main_vb), 6);
174         gtk_container_add(GTK_CONTAINER(tw->win), main_vb);
175
176     gtk_container_add(GTK_CONTAINER(main_vb), txt_scrollw);
177
178     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw),
179                                         GTK_SHADOW_IN);
180
181     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scrollw),
182                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
183     tw->txt = gtk_text_view_new();
184     gtk_text_view_set_editable(GTK_TEXT_VIEW(tw->txt), FALSE);
185     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tw->txt), GTK_WRAP_WORD);
186     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(tw->txt), FALSE);
187
188     gtk_text_view_set_left_margin(GTK_TEXT_VIEW(tw->txt), 4);
189     gtk_text_view_set_right_margin(GTK_TEXT_VIEW(tw->txt), 4);
190
191         hbox = gtk_hbox_new(FALSE, 0);
192     gtk_widget_show(hbox);
193
194     tw->button_hbox = gtk_hbutton_box_new();
195         gtk_button_box_set_layout(GTK_BUTTON_BOX(tw->button_hbox), GTK_BUTTONBOX_START);
196
197     gtk_box_pack_start(GTK_BOX(hbox), tw->button_hbox, TRUE, TRUE, 0);
198     gtk_widget_show(tw->button_hbox);
199
200         gtk_box_pack_start(GTK_BOX(main_vb), hbox, FALSE, FALSE, 0);
201
202         tw->bt_close = gtk_button_new_with_label("Close");
203 #if GTK_CHECK_VERSION(2,18,0)
204         gtk_widget_set_can_default(tw->bt_close, TRUE);
205 #else
206         GTK_WIDGET_SET_FLAGS(tw->bt_close, GTK_CAN_DEFAULT);
207 #endif
208         g_object_set_data(G_OBJECT(hbox), "Close", tw->bt_close);
209
210         gtk_box_pack_end(GTK_BOX(hbox), tw->bt_close, FALSE, FALSE, 0);
211         gtk_widget_show(tw->bt_close);
212
213         g_signal_connect(tw->bt_close, "clicked", G_CALLBACK(text_window_cancel_button_cb), tw);
214     gtk_widget_grab_default(tw->bt_close);
215
216         gtk_container_add(GTK_CONTAINER(txt_scrollw), tw->txt);
217     gtk_window_resize(GTK_WINDOW(tw->win),400,300);
218     gtk_widget_show_all(tw->win);
219
220     return tw;
221 }
222
223
224 static void text_window_clear(funnel_text_window_t*  tw)
225 {
226     GtkTextBuffer *buf;
227
228     if (! tw->win) return;
229
230     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tw->txt));
231
232     gtk_text_buffer_set_text(buf, "", 0);
233 }
234
235
236 static void text_window_append(funnel_text_window_t*  tw, const char *str)
237 {
238     GtkWidget *txt;
239     int nchars;
240     GtkTextBuffer *buf;
241     GtkTextIter    iter;
242
243     if (! tw->win) return;
244
245     txt = tw->txt;
246     nchars = (int) strlen(str);
247
248
249     buf= gtk_text_view_get_buffer(GTK_TEXT_VIEW(txt));
250
251     gtk_text_buffer_get_end_iter(buf, &iter);
252 #if GTK_CHECK_VERSION(3,0,0)
253     gtk_widget_override_font(GTK_WIDGET(txt), user_font_get_regular());
254 #else
255     gtk_widget_modify_font(GTK_WIDGET(txt), user_font_get_regular());
256 #endif
257     if (!g_utf8_validate(str, -1, NULL))
258         printf("Invalid utf8 encoding: %s\n", str);
259
260     gtk_text_buffer_insert(buf, &iter, str, nchars);
261 }
262
263
264 static void text_window_set_text(funnel_text_window_t*  tw, const gchar* text)
265 {
266     if (! tw->win) return;
267
268     text_window_clear(tw);
269     text_window_append(tw, text);
270 }
271
272
273 static void text_window_prepend(funnel_text_window_t*  tw, const char *str _U_) {
274     GtkWidget *txt;
275     int nchars;
276     GtkTextBuffer *buf;
277     GtkTextIter    iter;
278
279     if (! tw->win) return;
280
281     txt = tw->txt;
282     nchars = (int) strlen(str);
283
284
285     buf= gtk_text_view_get_buffer(GTK_TEXT_VIEW(txt));
286
287     gtk_text_buffer_get_start_iter(buf, &iter);
288  #if GTK_CHECK_VERSION(3,0,0)
289    gtk_widget_override_font(GTK_WIDGET(txt), user_font_get_regular());
290 #else
291    gtk_widget_modify_font(GTK_WIDGET(txt), user_font_get_regular());
292 #endif
293     if (!g_utf8_validate(str, -1, NULL))
294         printf("Invalid utf8 encoding: %s\n", str);
295
296     gtk_text_buffer_insert(buf, &iter, str, nchars);
297 }
298
299 static const gchar* text_window_get_text(funnel_text_window_t*  tw) {
300     GtkWidget *txt;
301     GtkTextBuffer *buf;
302     GtkTextIter    start;
303     GtkTextIter    end;
304
305     if (! tw->win) return "";
306
307         txt = tw->txt;
308
309     buf= gtk_text_view_get_buffer(GTK_TEXT_VIEW(txt));
310         gtk_text_buffer_get_start_iter(buf, &start);
311         gtk_text_buffer_get_end_iter(buf, &end);
312
313         return gtk_text_buffer_get_text(buf, &start, &end, FALSE);
314 }
315
316
317
318 static void text_window_set_close_cb(funnel_text_window_t*  tw, text_win_close_cb_t cb, void* data) {
319     tw->close_cb = cb;
320     tw->close_data = data;
321 }
322
323 static void text_window_destroy(funnel_text_window_t*  tw) {
324     if (tw->win) {
325         /*
326          * the window is still there and its callbacks refer to this data structure
327          * we need to change the callback so that they free tw.
328          */
329         g_signal_connect(tw->bt_close, "clicked", G_CALLBACK(unref_text_win_cancel_bt_cb), tw);
330         g_signal_connect(tw->win, "delete-event", G_CALLBACK(text_window_unref_del_event_cb), tw);
331     } else {
332                 unsigned i;
333         /*
334          * we have no window anymore a human user closed
335          * the window already just free the container
336          */
337                 for (i = 0; i < tw->buttons->len; i++) {
338                         funnel_bt_t* cbd = g_ptr_array_index(tw->buttons,i);
339                         /* XXX a free cb should be passed somehow */
340                         if (cbd->data && cbd->free_data_fcn) cbd->free_data_fcn(cbd->data);
341                         if (cbd->free_fcn) cbd->free_fcn(cbd);
342                 }
343                 g_ptr_array_free(tw->buttons,TRUE);
344         g_free(tw);
345     }
346 }
347
348 static void text_window_set_editable(funnel_text_window_t*  tw, gboolean editable){
349         gtk_text_view_set_editable(GTK_TEXT_VIEW(tw->txt), editable);
350         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(tw->txt), editable);
351 }
352
353 static gboolean text_window_button_cb(GtkWidget *bt _U_, gpointer user_data)
354 {
355         funnel_bt_t* cbd = user_data;
356
357         if (cbd->func) {
358                 return cbd->func(cbd->tw,cbd->data);
359         } else {
360                 return TRUE;
361         }
362 }
363
364 static void text_window_add_button(funnel_text_window_t*  tw, funnel_bt_t* cbd, const gchar* label) {
365         GtkWidget *button;
366
367         cbd->tw = tw;
368         g_ptr_array_add(tw->buttons,cbd);
369
370         button = gtk_button_new_with_label(label);
371 #if GTK_CHECK_VERSION(2,18,0)
372         gtk_widget_set_can_default(button, TRUE);
373 #else
374         GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
375 #endif
376
377         gtk_box_pack_start(GTK_BOX(tw->button_hbox), button, FALSE, FALSE, 0);
378
379         gtk_widget_show(button);
380         g_signal_connect(button, "clicked", G_CALLBACK(text_window_button_cb), cbd);
381
382 }
383
384
385 struct _funnel_dlg_data {
386     GtkWidget* win;
387     GPtrArray* entries;
388     funnel_dlg_cb_t dlg_cb;
389     void* data;
390 };
391
392 static gboolean funnel_dlg_cb(GtkWidget *win _U_, gpointer user_data)
393 {
394     struct _funnel_dlg_data* dd = user_data;
395     guint i;
396     guint len = dd->entries->len;
397     GPtrArray* returns = g_ptr_array_new();
398
399     for(i=0; i<len; i++) {
400         GtkEntry* entry = g_ptr_array_index(dd->entries,i);
401         g_ptr_array_add(returns,g_strdup(gtk_entry_get_text(entry)));
402     }
403
404     g_ptr_array_add(returns,NULL);
405
406     if (dd->dlg_cb)
407         dd->dlg_cb((gchar**)returns->pdata,dd->data);
408
409     window_destroy(GTK_WIDGET(dd->win));
410
411     g_ptr_array_free(returns,FALSE);
412
413     return TRUE;
414 }
415
416 static void funnel_cancel_btn_cb(GtkWidget *bt _U_, gpointer data) {
417     GtkWidget* win = data;
418
419     window_destroy(GTK_WIDGET(win));
420 }
421
422 static void funnel_new_dialog(const gchar* title,
423                                           const gchar** fieldnames,
424                                           funnel_dlg_cb_t dlg_cb,
425                                           void* data) {
426     GtkWidget *win, *main_tb, *main_vb, *bbox, *bt_cancel, *bt_ok;
427     guint i;
428     const gchar* fieldname;
429     struct _funnel_dlg_data* dd = g_malloc(sizeof(struct _funnel_dlg_data));
430
431     dd->entries = g_ptr_array_new();
432     dd->dlg_cb = dlg_cb;
433     dd->data = data;
434
435     for (i=0;fieldnames[i];i++);
436
437     win = dlg_window_new(title);
438
439     dd->win = win;
440
441     gtk_window_resize(GTK_WINDOW(win),400,10*(i+2));
442
443     main_vb = gtk_vbox_new(TRUE,5);
444     gtk_container_add(GTK_CONTAINER(win), main_vb);
445         gtk_container_set_border_width(GTK_CONTAINER(main_vb), 6);
446
447     main_tb = gtk_table_new(i+1, 2, FALSE);
448     gtk_box_pack_start(GTK_BOX(main_vb), main_tb, FALSE, FALSE, 0);
449     gtk_table_set_row_spacings(GTK_TABLE(main_tb), 10);
450     gtk_table_set_col_spacings(GTK_TABLE(main_tb), 15);
451
452     for (i = 0; (fieldname = fieldnames[i]) ; i++) {
453         GtkWidget *entry, *label;
454
455         label = gtk_label_new(fieldname);
456         gtk_misc_set_alignment(GTK_MISC(label), 1.0f, 0.5f);
457         gtk_table_attach_defaults(GTK_TABLE(main_tb), label, 0, 1, i+1, i + 2);
458         gtk_widget_show(label);
459
460         entry = gtk_entry_new();
461         g_ptr_array_add(dd->entries,entry);
462         gtk_table_attach_defaults(GTK_TABLE(main_tb), entry, 1, 2, i+1, i + 2);
463         gtk_widget_show(entry);
464     }
465
466     bbox = dlg_button_row_new(GTK_STOCK_CANCEL,GTK_STOCK_OK, NULL);
467     gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 0);
468
469     bt_ok = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_OK);
470     g_signal_connect(bt_ok, "clicked", G_CALLBACK(funnel_dlg_cb), dd);
471     gtk_widget_grab_default(bt_ok);
472
473     bt_cancel = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CANCEL);
474     g_signal_connect(bt_cancel, "clicked", G_CALLBACK(funnel_cancel_btn_cb), win);
475     gtk_widget_grab_default(bt_cancel);
476
477     gtk_widget_show(main_tb);
478     gtk_widget_show(main_vb);
479     gtk_widget_show(win);
480 }
481
482 static void funnel_set_filter(const char* filter_string) {
483         gtk_entry_set_text(GTK_ENTRY(main_display_filter_widget), filter_string);
484 }
485
486 static void funnel_set_color_filter_slot(guint8 filt_nr, const gchar* filter_string) {
487     color_filters_set_tmp(filt_nr, (gchar *)filter_string, FALSE);
488 }
489
490 static void funnel_apply_filter(void) {
491         const char* filter_string = gtk_entry_get_text(GTK_ENTRY(main_display_filter_widget));
492         main_filter_packets(&cfile, filter_string, FALSE);
493 }
494
495 /* XXX: finish this */
496 static void funnel_logger(const gchar *log_domain _U_,
497                           GLogLevelFlags log_level _U_,
498                           const gchar *message,
499                           gpointer user_data _U_) {
500     fputs(message,stderr);
501 }
502
503 static void funnel_retap_packets(void) {
504         cf_retap_packets(&cfile);
505 }
506
507 static gboolean funnel_open_file(const char* fname, const char* filter, const char** err_str) {
508         int err = 0;
509         dfilter_t   *rfcode = NULL;
510
511         *err_str = "no error";
512
513         switch (cfile.state) {
514                 case FILE_CLOSED:
515                 case FILE_READ_DONE:
516                 case FILE_READ_ABORTED:
517                         break;
518                 case FILE_READ_IN_PROGRESS:
519                         *err_str = "file read in progress";
520                         return FALSE;
521         }
522
523         if (filter) {
524                 if (!dfilter_compile(filter, &rfcode)) {
525                         *err_str = dfilter_error_msg ? dfilter_error_msg : "cannot compile filter";
526                         return FALSE;
527                 }
528         }
529
530
531         if (cf_open(&cfile, fname, FALSE, &err) != CF_OK) {
532                 *err_str = g_strerror(err);
533                 if (rfcode != NULL) dfilter_free(rfcode);
534                 return FALSE;
535         }
536
537         cfile.rfcode = rfcode;
538
539         switch (cf_read(&cfile, FALSE)) {
540                 case CF_READ_OK:
541                 case CF_READ_ERROR:
542                         break;
543                 default:
544                         *err_str = "problem while reading file";
545                         return FALSE;
546         }
547
548         return TRUE;
549 }
550
551 typedef struct progdlg _funnel_progress_window_t;
552
553 static funnel_progress_window_t* funnel_new_progress_window(const gchar* label, const gchar* task, gboolean terminate_is_stop, gboolean *stop_flag) {
554     return (funnel_progress_window_t*)create_progress_dlg(label, task, terminate_is_stop, stop_flag);
555 }
556
557 static void funnel_update_progress(funnel_progress_window_t* win, float pr, const gchar* task) {
558     update_progress_dlg((progdlg_t*)win, pr, task);
559 }
560
561 static void funnel_destroy_progress_window(funnel_progress_window_t* win) {
562     destroy_progress_dlg((progdlg_t*)win);
563 }
564
565 static void funnel_reload(void) {
566         if (cfile.state == FILE_READ_DONE) cf_reload(&cfile);
567 }
568
569 static const funnel_ops_t funnel_ops = {
570     new_text_window,
571     text_window_set_text,
572     text_window_append,
573     text_window_prepend,
574     text_window_clear,
575     text_window_get_text,
576     text_window_set_close_cb,
577         text_window_set_editable,
578     text_window_destroy,
579         text_window_add_button,
580     /*...,*/
581     funnel_new_dialog,
582     funnel_logger,
583         funnel_retap_packets,
584         copy_to_clipboard,
585         funnel_set_filter,
586         funnel_set_color_filter_slot,
587         funnel_open_file,
588         funnel_reload,
589         funnel_apply_filter,
590         browser_open_url,
591         browser_open_data_file,
592     funnel_new_progress_window,
593     funnel_update_progress,
594     funnel_destroy_progress_window
595 };
596
597
598 typedef struct _menu_cb_t {
599     void (*callback)(gpointer);
600     void* callback_data;
601     gboolean retap;
602 } menu_cb_t;
603
604 static void our_menu_callback(void* unused _U_, gpointer data) {
605     menu_cb_t* mcb = data;
606     mcb->callback(mcb->callback_data);
607     if (mcb->retap) cf_retap_packets(&cfile);
608 }
609
610 static const char* stat_group_name(register_stat_group_t group _U_) {
611     static const value_string VALS_GROUP_NAMES[] = {
612         {REGISTER_ANALYZE_GROUP_UNSORTED,            "/Menubar/AnalyzeMenu|_Analyze"},               /* unsorted analyze stuff */
613         {REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER, "/Menubar/AnalyzeMenu/ConversationFilterMenu|Conversation Filter"}, /* conversation filters */
614         {REGISTER_STAT_GROUP_UNSORTED,               "/Menubar/StatisticsMenu|Statistics"},          /* unsorted statistic function */
615         {REGISTER_STAT_GROUP_GENERIC,                "/Menubar/StatisticsMenu|Statistics"},          /* generic statistic function, not specific to a protocol */
616         {REGISTER_STAT_GROUP_CONVERSATION_LIST,      "/Menubar/StatisticsMenu|Statistics/ConversationListMenu|_Conversation List"},        /* member of the conversation list */
617         {REGISTER_STAT_GROUP_ENDPOINT_LIST,          "/Menubar/StatisticsMenu|Statistics/EndpointListMenu|_Endpoint List"},                /* member of the endpoint list */
618         {REGISTER_STAT_GROUP_RESPONSE_TIME,          "/Menubar/StatisticsMenu|Statistics/ServiceResponseTimeMenu|Service _Response Time"}, /* member of the service response time list */
619         {REGISTER_STAT_GROUP_TELEPHONY,              "/Menubar/TelephonyMenu|Telephon_y"},           /* telephony specific */
620         {REGISTER_TOOLS_GROUP_UNSORTED,              "/Menubar/ToolsMenu|_Tools"},                   /* unsorted tools */
621                 { 0, NULL}
622     };
623     return val_to_str_const(group, VALS_GROUP_NAMES, "/Menubar/ToolsMenu|_Tools");
624 }
625
626 static void register_menu_cb(const char *name,
627                              register_stat_group_t group _U_,
628                              void (*callback)(gpointer),
629                              gpointer callback_data,
630                              gboolean retap) {
631
632     menu_cb_t* mcb = g_malloc(sizeof(menu_cb_t));
633         const char *label = NULL, *str = NULL;
634
635     mcb->callback = callback;
636     mcb->callback_data = callback_data;
637     mcb->retap = retap;
638
639         str = strrchr(name,'/');
640         if(str){
641                 label = str+1;
642         }else{
643                 label = name;
644         }
645
646         register_lua_menu_bar_menu_items(
647                 stat_group_name(group), /* GUI path to the place holder in the menu */
648                 name, /* Action name */
649                 NULL, /* Stock id */
650                 label, /* label */
651                 NULL, /* Accelerator */
652                 NULL, /* Tooltip */
653                 our_menu_callback, /* Callback */
654                 mcb,               /* callback data */
655                 TRUE,              /* enabled */
656                 NULL,
657                 NULL);
658 }
659
660 void initialize_funnel_ops(void) {
661     funnel_set_funnel_ops(&funnel_ops);
662 }
663
664 void
665 register_tap_listener_gtkfunnel(void)
666 {
667     funnel_register_all_menus(register_menu_cb);
668 }