2 * GTK Tap implementation of stats_tree
3 * 2005, Luis E. G. Ontanon
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 /* stats_tree modifications by Deon van der Westhuysen, November 2013
28 * - sorting by column,
29 * - display a generic number of columns(driven by stats_tree.c
31 * - export to text, CSV or XML file
39 #include <wsutil/report_err.h>
40 #include <wsutil/file_util.h>
42 #include <epan/stats_tree_priv.h>
44 #include "ui/simple_dialog.h"
45 #include "../globals.h"
46 #include "../stat_menu.h"
48 #include "ui/gtk/gui_utils.h"
49 #include "ui/gtk/dlg_utils.h"
50 #include "ui/gtk/file_dlg.h"
51 #include "ui/gtk/tap_param_dlg.h"
52 #include "ui/gtk/main.h"
54 #include "ui/gtk/old-gtk-compat.h"
56 #include "ui/gtk/gui_stat_menu.h"
59 #define USE_WIN32_FILE_DIALOGS
62 #ifdef USE_WIN32_FILE_DIALOGS
63 #include <gdk/gdkwin32.h>
65 #include "ui/win32/file_dlg_win32.h"
68 void register_tap_listener_stats_tree_stat(void);
70 struct _st_node_pres {
74 struct _tree_cfg_pres {
75 tap_param_dlg* stat_dlg;
85 /* Define fixed column indexes */
86 #define NODEPTR_COLUMN 0 /* Always first column */
87 #define N_RESERVED_COL 1 /* Number of columns for internal use - added before visible cols */
91 draw_gtk_node(stat_node* node)
93 GtkTreeIter* parent = NULL;
95 int num_columns= node->st->num_columns+N_RESERVED_COL;
96 gint *columns = (gint*) g_malloc(sizeof(gint)*num_columns);
97 GValue *values = (GValue*) g_malloc0(sizeof(GValue)*num_columns);
98 gchar **valstrs = stats_tree_get_values_from_node(node);
102 g_value_init(values, G_TYPE_POINTER);
103 g_value_set_pointer(values, node);
104 for (count = N_RESERVED_COL; count<num_columns; count++) {
105 columns[count]= count;
106 g_value_init(values+count, G_TYPE_STRING);
107 g_value_take_string (values+count,valstrs[count-N_RESERVED_COL]);
111 node->pr = (st_node_pres *)g_malloc(sizeof(st_node_pres));
113 if (node->st->pr->store) {
114 node->pr->iter = (GtkTreeIter *)g_malloc0(sizeof(GtkTreeIter));
116 if ( node->parent && node->parent->pr ) {
117 parent = node->parent->pr->iter;
119 gtk_tree_store_append (node->st->pr->store, node->pr->iter, parent);
120 gtk_tree_store_set_valuesv(node->st->pr->store, node->pr->iter,
121 columns, values, num_columns);
124 if (node->st->pr->store && node->pr->iter) {
125 /* skip reserved columns and first entry in the stats_tree values */
126 /* list (the node name). These should already be set and static. */
127 gtk_tree_store_set_valuesv(node->st->pr->store, node->pr->iter,
128 columns+N_RESERVED_COL+1, values+N_RESERVED_COL+1,
129 num_columns-N_RESERVED_COL-1);
132 for (count = 0; count<num_columns; count++) {
133 g_value_unset(values+count);
139 if (node->children) {
140 for (child = node->children; child; child = child->next )
141 draw_gtk_node(child);
147 draw_gtk_tree(void *psp)
149 stats_tree *st = (stats_tree *)psp;
152 gint sort_column= GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
153 GtkSortType order= GTK_SORT_DESCENDING;
155 for (count = 0; count<st->num_columns; count++) {
156 gtk_tree_view_column_set_title(gtk_tree_view_get_column(GTK_TREE_VIEW(st->pr->tree),count),
157 stats_tree_get_column_name(count));
160 gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order);
161 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (st->pr->store),
162 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_DESCENDING);
164 for (child = st->root.children; child; child = child->next ) {
165 draw_gtk_node(child);
167 if ( (!(child->st_flags&ST_FLG_DEF_NOEXPAND)) && child->pr->iter && st->pr->store ) {
168 gtk_tree_view_expand_row(GTK_TREE_VIEW(st->pr->tree),
169 gtk_tree_model_get_path(GTK_TREE_MODEL(st->pr->store),child->pr->iter),
174 if ((sort_column==GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID)||
175 (sort_column==GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID)) {
176 sort_column= stats_tree_get_default_sort_col(st)+N_RESERVED_COL;
177 order= stats_tree_is_default_sort_DESC(st)?GTK_SORT_DESCENDING:GTK_SORT_ASCENDING;
180 /* Only call this once the entire list is drawn - else Gtk seems */
181 /* to get sorting order wrong (sorting broken when new nodes are */
182 /* added after setting sort column.) Also for performance. */
183 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), sort_column, order);
187 copy_tree_to_clipboard
188 (GtkWidget *win _U_, stats_tree *st)
190 gint sort_column= N_RESERVED_COL; /* default */
191 GtkSortType order= GTK_SORT_DESCENDING;
194 gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order);
195 s= stats_tree_format_as_str(st,ST_FORMAT_PLAIN,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING);
196 copy_to_clipboard(s);
197 g_string_free (s,TRUE);
203 #ifndef USE_WIN32_FILE_DIALOGS
205 gtk_save_as_statstree(GtkWidget *win, GString *file_name, int *file_type)
209 GtkWidget *ft_hb, *ft_lb, *ft_combo_box;
213 saveas_w = file_selection_new("Wireshark: Save stats tree as ...",
214 GTK_WINDOW(win), FILE_SELECTION_SAVE);
216 main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 5, FALSE);
217 gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
218 file_selection_set_extra_widget(saveas_w, main_vb);
219 gtk_widget_show(main_vb);
222 ft_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
223 gtk_box_pack_start(GTK_BOX(main_vb), ft_hb, FALSE, FALSE, 0);
224 gtk_widget_show(ft_hb);
226 ft_lb = gtk_label_new("Save as format:");
227 gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0);
228 gtk_widget_show(ft_lb);
230 ft_combo_box = ws_combo_box_new_text_and_pointer();
231 ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "Plain text file (.txt)", GINT_TO_POINTER(ST_FORMAT_PLAIN));
232 ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "Comma separated values (.csv)", GINT_TO_POINTER(ST_FORMAT_CSV));
233 ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "XML document (.xml)", GINT_TO_POINTER(ST_FORMAT_XML));
234 ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box), "YAML document (.yaml)", GINT_TO_POINTER(ST_FORMAT_YAML));
236 gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0);
237 gtk_widget_show(ft_combo_box);
238 ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0);
240 st_name = file_selection_run(saveas_w);
241 if (st_name == NULL) {
242 /* User cancelled or closed the dialog. */
246 if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) {
247 g_assert_not_reached(); /* Programming error: somehow nothing is active */
250 /* Save result from dialog box */
251 *file_type = GPOINTER_TO_INT(ptr);
252 g_string_printf(file_name, "%s", st_name);
254 /* We've crossed the Rubicon; get rid of the file save-as box. */
255 window_destroy(GTK_WIDGET(saveas_w));
259 #endif /* USE_WIN32_FILE_DIALOGS */
262 save_as_dialog(GtkWidget *win _U_, stats_tree *st)
264 gint sort_column= 1; /* default */
265 GtkSortType order= GTK_SORT_DESCENDING;
267 GString *file_name = g_string_new("");
269 gchar *file_name_lower;
270 const gchar *file_ext;
272 gboolean success= FALSE;
275 #ifdef USE_WIN32_FILE_DIALOGS
276 if (win32_save_as_statstree(GDK_WINDOW_HWND(gtk_widget_get_window(st->pr->win)),
277 file_name, &file_type)) {
278 #else /* USE_WIN32_FILE_DIALOGS */
279 if (gtk_save_as_statstree(st->pr->win,file_name,&file_type)) {
280 #endif /* USE_WIN32_FILE_DIALOGS */
282 /* add file extension as required */
283 file_name_lower = g_utf8_strdown(file_name->str, -1);
285 case ST_FORMAT_YAML: file_ext = ".yaml";
287 case ST_FORMAT_XML: file_ext = ".xml";
289 case ST_FORMAT_CSV: file_ext = ".csv";
291 default: file_ext = ".txt";
294 if (!g_str_has_suffix(file_name_lower, file_ext)) {
295 /* Must add extenstion */
296 g_string_append(file_name,file_ext);
298 g_free(file_name_lower);
300 gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (st->pr->store), &sort_column, &order);
301 str_tree=stats_tree_format_as_str(st,(st_format_type)file_type,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING);
303 /* actually save the file */
304 f= ws_fopen (file_name->str,"w");
307 if (fputs(str_tree->str, f)!=EOF) {
314 GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW(st->pr->win),
315 GTK_DIALOG_DESTROY_WITH_PARENT,
318 "Error saving file '%s': %s",
319 file_name->str, g_strerror (last_errno));
320 gtk_dialog_run (GTK_DIALOG (dialog));
321 gtk_widget_destroy (dialog);
324 g_string_free(str_tree, TRUE);
327 g_string_free(file_name, TRUE);
333 free_gtk_tree(GtkWindow *win _U_, stats_tree *st)
335 remove_tap_listener(st);
338 st->root.pr->iter = NULL;
340 st->cfg->in_use = FALSE;
346 clear_node_pr(stat_node* n)
349 for (c = n->children; c; c = c->next) {
354 gtk_tree_store_remove(n->st->pr->store, n->pr->iter);
362 stats_tree* st = (stats_tree *)p;
365 for (c = st->root.children; c; c = c->next) {
369 stats_tree_reinit(st);
370 /* st->cfg->init(st); doesn't properly delete nodes */
374 st_sort_func(GtkTreeModel *model,
379 gint sort_column= 1; /* default */
380 GtkSortType order= GTK_SORT_DESCENDING;
385 gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (user_data), &sort_column, &order);
387 gtk_tree_model_get(model, a, NODEPTR_COLUMN, &node_a, -1);
388 gtk_tree_model_get(model, b, NODEPTR_COLUMN, &node_b, -1);
390 result= stats_tree_sort_compare(node_a,node_b,sort_column-N_RESERVED_COL,order==GTK_SORT_DESCENDING);
391 if (order==GTK_SORT_DESCENDING) {
397 /* initializes the stats_tree window */
399 init_gtk_tree(const char* opt_arg, void *userdata _U_)
401 gchar *abbr = stats_tree_get_abbr(opt_arg);
402 stats_tree* st = NULL;
403 stats_tree_cfg* cfg = NULL;
404 tree_pres* pr = (tree_pres *)g_malloc(sizeof(tree_pres));
406 gchar* window_name = NULL;
407 GString* error_string;
410 GtkWidget *main_vb, *bbox, *bt_close, *bt_copy, *bt_saveas;
411 GtkTreeViewColumn* column;
412 GtkCellRenderer* renderer;
413 GtkTreeSortable *sortable;
418 cfg = stats_tree_get_cfg_by_abbr(abbr);
420 if (cfg && cfg->in_use) {
422 report_failure("cannot open more than one tree of the same type at once");
427 init_strlen = strlen(cfg->pr->stat_dlg->init_string);
429 if (strncmp (opt_arg, cfg->pr->stat_dlg->init_string, init_strlen) == 0){
430 if (init_strlen == strlen(opt_arg)) {
431 st = stats_tree_new(cfg,pr,NULL);
433 st = stats_tree_new(cfg,pr,opt_arg+init_strlen+1);
437 st = stats_tree_new(cfg,pr,NULL);
440 report_failure("no such stats_tree (%s) in stats_tree registry",abbr);
447 report_failure("could not obtain stats_tree abbr from opt_arg");
454 window_name = g_strdup_printf("%s Stats Tree", st->display_name);
456 st->pr->win = window_new_with_geom(GTK_WINDOW_TOPLEVEL,window_name,window_name);
457 gtk_window_set_default_size(GTK_WINDOW(st->pr->win), st->num_columns*80+80, 400);
461 title=g_strdup_printf("%s with filter: %s",st->display_name,st->filter);
464 title=g_strdup_printf("%s", st->display_name);
467 gtk_window_set_title(GTK_WINDOW(st->pr->win), title);
470 main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
471 gtk_container_set_border_width(GTK_CONTAINER(main_vb), 12);
472 gtk_container_add(GTK_CONTAINER(st->pr->win), main_vb);
474 scr_win = scrolled_window_new(NULL, NULL);
476 col_types= (GType*)g_malloc(sizeof(GType)*(st->num_columns+N_RESERVED_COL));
477 col_types[0] = G_TYPE_POINTER;
478 for (count = 0; count<st->num_columns; count++) {
479 col_types[count+N_RESERVED_COL] = G_TYPE_STRING;
481 st->pr->store = gtk_tree_store_newv (st->num_columns+N_RESERVED_COL,col_types);
484 sortable= GTK_TREE_SORTABLE (st->pr->store);
485 st->pr->tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (st->pr->store));
486 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(st->pr->tree), FALSE);
487 g_object_unref(G_OBJECT(st->pr->store));
489 gtk_container_add( GTK_CONTAINER(scr_win), st->pr->tree);
492 for (count = 0; count<st->num_columns; count++) {
493 renderer = gtk_cell_renderer_text_new ();
494 column = gtk_tree_view_column_new_with_attributes (stats_tree_get_column_name(count),
495 renderer, "text", count+N_RESERVED_COL, NULL);
496 gtk_tree_view_column_set_sort_column_id(column, count+N_RESERVED_COL);
497 gtk_tree_sortable_set_sort_func(sortable,count+N_RESERVED_COL, st_sort_func, sortable, NULL);
498 gtk_tree_view_column_set_resizable (column,TRUE);
499 gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE);
500 gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column);
503 gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL);
505 gtk_box_pack_start(GTK_BOX(main_vb), scr_win, TRUE, TRUE, 0);
507 error_string = register_tap_listener( cfg->tapname,
516 /* error, we failed to attach to the tap. clean up */
517 /* destroy_stat_tree_window(st); */
518 report_failure("stats_tree for: %s failed to attach to the tap: %s",cfg->name,error_string->str);
519 g_string_free(error_string, TRUE);
523 bbox = dlg_button_row_new(GTK_STOCK_COPY, GTK_STOCK_SAVE_AS, GTK_STOCK_CLOSE, NULL);
524 gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 0);
526 bt_close = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CLOSE);
527 window_set_cancel_button(st->pr->win, bt_close, window_cancel_button_cb);
529 g_signal_connect(GTK_WINDOW(st->pr->win), "delete_event", G_CALLBACK(window_delete_event_cb), NULL);
530 g_signal_connect(GTK_WINDOW(st->pr->win), "destroy", G_CALLBACK(free_gtk_tree), st);
532 bt_copy = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_COPY);
533 g_signal_connect(GTK_WINDOW (bt_copy), "clicked", G_CALLBACK(copy_tree_to_clipboard), st);
535 bt_saveas = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_SAVE_AS);
536 g_signal_connect(GTK_WINDOW (bt_saveas), "clicked", G_CALLBACK(save_as_dialog), st);
538 gtk_widget_show_all(st->pr->win);
539 window_present(st->pr->win);
541 cf_retap_packets(&cfile);
542 gdk_window_raise(gtk_widget_get_window(st->pr->win));
545 static tap_param tree_stat_params[] = {
546 { PARAM_FILTER, "Filter", NULL }
550 register_gtk_stats_tree_tap (gpointer k _U_, gpointer v, gpointer p _U_)
552 stats_tree_cfg* cfg = (stats_tree_cfg *)v;
553 gchar* display_name= stats_tree_get_displayname(cfg->name);
555 cfg->pr = (tree_cfg_pres *)g_malloc(sizeof(tree_cfg_pres));
557 cfg->pr->stat_dlg = (tap_param_dlg *)g_malloc(sizeof(tap_param_dlg));
559 cfg->pr->stat_dlg->win_title = g_strdup_printf("%s Stats Tree",display_name);
560 cfg->pr->stat_dlg->init_string = g_strdup_printf("%s,tree",cfg->abbr);
561 cfg->pr->stat_dlg->tap_init_cb = init_gtk_tree;
562 cfg->pr->stat_dlg->index = -1;
563 cfg->pr->stat_dlg->nparams = G_N_ELEMENTS(tree_stat_params);
564 cfg->pr->stat_dlg->params = tree_stat_params;
565 g_free(display_name);
569 free_tree_presentation(stats_tree* st)
575 register_tap_listener_stats_tree_stat(void)
578 stats_tree_presentation(register_gtk_stats_tree_tap,
584 free_tree_presentation,
590 void gtk_stats_tree_cb(GtkAction *action, gpointer user_data _U_)
592 const gchar *action_name;
594 stats_tree_cfg* cfg = NULL;
596 action_name = gtk_action_get_name (action);
597 abbr = strrchr(action_name,'/');
601 abbr = g_strdup_printf("%s",action_name);
603 cfg = stats_tree_get_cfg_by_abbr(abbr);
605 tap_param_dlg_cb(action, cfg->pr->stat_dlg);
607 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
608 "Failed to find the stat tree named %s",