ba7d66861576a2dd17cff5667080d91ada113467
[obnox/wireshark/wip.git] / gtk / main_proto_draw.c
1 /* proto_draw.c
2  * Routines for GTK+ packet display
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * Jeff Foster,    2001/03/12,  added support for displaying named
11  *                              data sources as tabbed hex windows
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <ctype.h>
33
34 #ifdef HAVE_FCNTL_H
35 #include <fcntl.h>
36 #endif
37
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41
42 #include <gtk/gtk.h>
43 #include <gdk/gdkkeysyms.h>
44
45 #include <string.h>
46
47 #include <epan/epan_dissect.h>
48
49 #include <epan/packet.h>
50 #include <epan/charsets.h>
51 #include <epan/prefs.h>
52 #include <epan/filesystem.h>
53
54 #include "../isprint.h"
55 #include "../alert_box.h"
56 #include "../simple_dialog.h"
57 #include "../progress_dlg.h"
58 #include "../ui_util.h"
59 #include <wsutil/file_util.h>
60
61 #include "gtk/keys.h"
62 #include "gtk/color_utils.h"
63 #include "gtk/capture_file_dlg.h"
64 #include "gtk/packet_win.h"
65 #include "gtk/file_dlg.h"
66 #include "gtk/gui_utils.h"
67 #include "gtk/gtkglobals.h"
68 #include "gtk/font_utils.h"
69 #include "gtk/webbrowser.h"
70 #include "gtk/main.h"
71 #include "gtk/main_menu.h"
72 #include "gtk/main_proto_draw.h"
73
74 #if _WIN32
75 #include <gdk/gdkwin32.h>
76 #include <windows.h>
77 #include "file_dlg_win32.h"
78 #endif
79
80
81 #define BYTE_VIEW_WIDTH    16
82 #define BYTE_VIEW_SEP      8
83
84 #define E_BYTE_VIEW_TREE_PTR      "byte_view_tree_ptr"
85 #define E_BYTE_VIEW_TREE_VIEW_PTR "byte_view_tree_view_ptr"
86 #define E_BYTE_VIEW_NDIGITS_KEY   "byte_view_ndigits"
87 #define E_BYTE_VIEW_TVBUFF_KEY    "byte_view_tvbuff"
88 #define E_BYTE_VIEW_START_KEY     "byte_view_start"
89 #define E_BYTE_VIEW_END_KEY       "byte_view_end"
90 #define E_BYTE_VIEW_APP_START_KEY "byte_view_app_start"
91 #define E_BYTE_VIEW_APP_END_KEY   "byte_view_app_end"
92 #define E_BYTE_VIEW_ENCODE_KEY    "byte_view_encode"
93
94 static GtkWidget *
95 add_byte_tab(GtkWidget *byte_nb, const char *name, tvbuff_t *tvb,
96     proto_tree *tree, GtkWidget *tree_view);
97
98 static void
99 proto_tree_draw_node(proto_node *node, gpointer data);
100
101 /* Get the current text window for the notebook. */
102 GtkWidget *
103 get_notebook_bv_ptr(GtkWidget *nb_ptr)
104 {
105   int num;
106   GtkWidget *bv_page;
107
108   num = gtk_notebook_get_current_page(GTK_NOTEBOOK(nb_ptr));
109   bv_page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(nb_ptr), num);
110   if (bv_page)
111       return GTK_BIN(bv_page)->child;
112   else
113       return NULL;
114 }
115
116 /*
117  * Get the data and length for a byte view, given the byte view page.
118  * Return the pointer, or NULL on error, and set "*data_len" to the length.
119  */
120 const guint8 *
121 get_byte_view_data_and_length(GtkWidget *byte_view, guint *data_len)
122 {
123   tvbuff_t *byte_view_tvb;
124   const guint8 *data_ptr;
125
126   byte_view_tvb = g_object_get_data(G_OBJECT(byte_view), E_BYTE_VIEW_TVBUFF_KEY);
127   if (byte_view_tvb == NULL)
128     return NULL;
129
130   data_ptr = tvb_get_ptr(byte_view_tvb, 0, -1);
131   *data_len = tvb_length(byte_view_tvb);
132   return data_ptr;
133 }
134
135 /*
136  * Set the current text window for the notebook to the window that
137  * refers to a particular tvbuff.
138  */
139 void
140 set_notebook_page(GtkWidget *nb_ptr, tvbuff_t *tvb)
141 {
142   int num;
143   GtkWidget *bv_page, *bv;
144   tvbuff_t *bv_tvb;
145
146   for (num = 0;
147        (bv_page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(nb_ptr), num)) != NULL;
148        num++) {
149     bv = GTK_BIN(bv_page)->child;
150     bv_tvb = g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_TVBUFF_KEY);
151     if (bv_tvb == tvb) {
152       /* Found it. */
153       gtk_notebook_set_current_page(GTK_NOTEBOOK(nb_ptr), num);
154       break;
155     }
156   }
157 }
158
159 /* Redraw a given byte view window. */
160 void
161 redraw_hex_dump(GtkWidget *nb, frame_data *fd, field_info *finfo)
162 {
163   GtkWidget *bv;
164   const guint8 *data;
165   guint len;
166
167   bv = get_notebook_bv_ptr(nb);
168   if (bv != NULL) {
169     data = get_byte_view_data_and_length(bv, &len);
170     if (data != NULL)
171       packet_hex_print(bv, data, fd, finfo, len);
172   }
173 }
174
175 /* Redraw all byte view windows. */
176 void
177 redraw_hex_dump_all(void)
178 {
179     if (cfile.current_frame != NULL)
180             redraw_hex_dump( byte_nb_ptr, cfile.current_frame, cfile.finfo_selected);
181
182   redraw_hex_dump_packet_wins();
183
184   /* XXX - this is a hack, to workaround a bug in GTK2.x!
185      when changing the font size, even refilling of the corresponding
186      gtk_text_buffer doesn't seem to trigger an update.
187      The only workaround is to freshly select the frame, which will remove any
188      existing notebook tabs and "restart" the whole byte view again. */
189   if (cfile.current_frame != NULL) {
190     cfile.current_row = -1;
191     cf_goto_frame(&cfile, cfile.current_frame->num);
192   }
193 }
194
195 static void
196 expand_tree(GtkTreeView *tree_view, GtkTreeIter *iter,
197             GtkTreePath *path _U_, gpointer user_data _U_)
198 {
199     field_info   *finfo;
200     GtkTreeModel *model;
201
202     model = gtk_tree_view_get_model(tree_view);
203     gtk_tree_model_get(model, iter, 1, &finfo, -1);
204     g_assert(finfo);
205
206     /*
207      * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
208      * are thus presumably leaf nodes and cannot be expanded.
209      */
210     if (finfo->tree_type != -1) {
211         g_assert(finfo->tree_type >= 0 &&
212                  finfo->tree_type < num_tree_types);
213         tree_is_expanded[finfo->tree_type] = TRUE;
214     }
215 }
216
217 static void
218 collapse_tree(GtkTreeView *tree_view, GtkTreeIter *iter,
219             GtkTreePath *path _U_, gpointer user_data _U_)
220 {
221     field_info   *finfo;
222     GtkTreeModel *model;
223
224     model = gtk_tree_view_get_model(tree_view);
225     gtk_tree_model_get(model, iter, 1, &finfo, -1);
226     g_assert(finfo);
227
228     /*
229      * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
230      * are thus presumably leaf nodes and cannot be collapsed.
231      */
232     if (finfo->tree_type != -1) {
233         g_assert(finfo->tree_type >= 0 &&
234                  finfo->tree_type < num_tree_types);
235         tree_is_expanded[finfo->tree_type] = FALSE;
236     }
237 }
238
239 #define MAX_OFFSET_LEN  8       /* max length of hex offset of bytes */
240 #define BYTES_PER_LINE  16      /* max byte values in a line */
241 #define HEX_DUMP_LEN    (BYTES_PER_LINE*3 + 1)
242                                 /* max number of characters hex dump takes -
243                                    2 digits plus trailing blank
244                                    plus separator between first and
245                                    second 8 digits */
246 #define DATA_DUMP_LEN   (HEX_DUMP_LEN + 2 + BYTES_PER_LINE)
247                                 /* number of characters those bytes take;
248                                    3 characters per byte of hex dump,
249                                    2 blanks separating hex from ASCII,
250                                    1 character per byte of ASCII dump */
251 #define MAX_LINE_LEN    (MAX_OFFSET_LEN + 2 + DATA_DUMP_LEN)
252                                 /* number of characters per line;
253                                    offset, 2 blanks separating offset
254                                    from data dump, data dump */
255 #define MAX_LINES       100
256 #define MAX_LINES_LEN   (MAX_LINES*MAX_LINE_LEN)
257
258 /* Which byte the offset is referring to. Associates
259  * whitespace with the preceding digits. */
260 static int
261 byte_num(int offset, int start_point)
262 {
263         return (offset - start_point) / 3;
264 }
265
266 struct field_lookup_info {
267     field_info  *fi;
268     GtkTreeIter  iter;
269 };
270
271 static gboolean
272 lookup_finfo(GtkTreeModel *model, GtkTreePath *path _U_, GtkTreeIter *iter,
273             gpointer data)
274 {
275     field_info *fi;
276     struct field_lookup_info *fli = (struct field_lookup_info *)data;
277
278     gtk_tree_model_get(model, iter, 1, &fi, -1);
279     if (fi == fli->fi) {
280         fli->iter = *iter;
281         return TRUE;
282     }
283     return FALSE;
284 }
285
286 GtkTreePath *tree_find_by_field_info(GtkTreeView *tree_view, field_info *finfo) {
287   GtkTreeModel *model;
288   struct field_lookup_info fli;
289
290   g_assert(finfo != NULL);
291
292   model = gtk_tree_view_get_model(tree_view);
293   fli.fi = finfo;
294   gtk_tree_model_foreach(model, lookup_finfo, &fli);
295
296   return gtk_tree_model_get_path(model, &fli.iter);
297 }
298
299 /* If the user selected a certain byte in the byte view, try to find
300  * the item in the GUI proto_tree that corresponds to that byte, and:
301  *
302  *      if we succeed, select it, and return TRUE;
303  *      if we fail, return FALSE. */
304 gboolean
305 byte_view_select(GtkWidget *widget, GdkEventButton *event)
306 {
307     proto_tree   *tree;
308     GtkTreeView  *tree_view;
309     GtkTextView  *bv = GTK_TEXT_VIEW(widget);
310     gint          x, y;
311     GtkTextIter   iter;
312     int           row, column;
313     int           byte;
314     tvbuff_t     *tvb;
315     guint         ndigits;
316     int           digits_start_1;
317     int           digits_end_1;
318     int           digits_start_2;
319     int           digits_end_2;
320     int           text_start_1;
321     int           text_end_1;
322     int           text_start_2;
323     int           text_end_2;
324
325     /*
326      * Get the number of digits of offset being displayed, and
327      * compute the columns of various parts of the display.
328      */
329     ndigits = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_NDIGITS_KEY));
330
331     /*
332      * The column of the first hex digit in the first half.
333      * That starts after "ndigits" digits of offset and two
334      * separating blanks.
335      */
336     digits_start_1 = ndigits + 2;
337
338     /*
339      * The column of the last hex digit in the first half.
340      * There are BYTES_PER_LINE/2 bytes displayed in the first
341      * half; there are 2 characters per byte, plus a separating
342      * blank after all but the last byte's characters.
343      *
344      * Then subtract 1 to get the last column of the first half
345      * rather than the first column after the first half.
346      */
347     digits_end_1 = digits_start_1 + (BYTES_PER_LINE/2)*2 +
348         (BYTES_PER_LINE/2 - 1) - 1;
349
350     /*
351      * The column of the first hex digit in the second half.
352      * Add back the 1 to get the first column after the first
353      * half, and then add 2 for the 2 separating blanks between
354      * the halves.
355      */
356     digits_start_2 = digits_end_1 + 3;
357
358     /*
359      * The column of the last hex digit in the second half.
360      * Add the same value we used to get "digits_end_1" from
361      * "digits_start_1".
362      */
363     digits_end_2 = digits_start_2 + (BYTES_PER_LINE/2)*2 +
364         (BYTES_PER_LINE/2 - 1) - 1;
365
366     /*
367      * The column of the first "text dump" character in the first half.
368      * Add back the 1 to get the first column after the second
369      * half's hex dump, and then add 3 for the 3 separating blanks
370      * between the hex and text dummp.
371      */
372     text_start_1 = digits_end_2 + 4;
373
374     /*
375      * The column of the last "text dump" character in the first half.
376      * There are BYTES_PER_LINE/2 bytes displayed in the first
377      * half; there is 1 character per byte.
378      *
379      * Then subtract 1 to get the last column of the first half
380      * rather than the first column after the first half.
381      */
382     text_end_1 = text_start_1 + BYTES_PER_LINE/2 - 1;
383
384     /*
385      * The column of the first "text dump" character in the second half.
386      * Add back the 1 to get the first column after the first half,
387      * and then add 1 for the separating blank between the halves.
388      */
389     text_start_2 = text_end_1 + 2;
390
391     /*
392      * The column of the last "text dump" character in second half.
393      * Add the same value we used to get "text_end_1" from
394      * "text_start_1".
395      */
396     text_end_2 = text_start_2 + BYTES_PER_LINE/2 - 1;
397
398     tree = g_object_get_data(G_OBJECT(widget), E_BYTE_VIEW_TREE_PTR);
399     if (tree == NULL) {
400         /*
401          * Somebody clicked on the dummy byte view; do nothing.
402          */
403         return FALSE;
404     }
405     tree_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(widget),
406                                               E_BYTE_VIEW_TREE_VIEW_PTR));
407
408     /* get the row/column selected */
409     gtk_text_view_window_to_buffer_coords(bv,
410                          gtk_text_view_get_window_type(bv, event->window),
411                          (gint) event->x, (gint) event->y, &x, &y);
412     gtk_text_view_get_iter_at_location(bv, &iter, x, y);
413     row = gtk_text_iter_get_line(&iter);
414     column = gtk_text_iter_get_line_offset(&iter);
415
416     /* Given the column and row, determine which byte offset
417      * the user clicked on. */
418     if (column >= digits_start_1 && column <= digits_end_1) {
419         byte = byte_num(column, digits_start_1);
420         if (byte == -1) {
421             return FALSE;
422         }
423     }
424     else if (column >= digits_start_2 && column <= digits_end_2) {
425         byte = byte_num(column, digits_start_2);
426         if (byte == -1) {
427             return FALSE;
428         }
429         byte += 8;
430     }
431     else if (column >= text_start_1 && column <= text_end_1) {
432         byte = column - text_start_1;
433     }
434     else if (column >= text_start_2 && column <= text_end_2) {
435         byte = 8 + column - text_start_2;
436     }
437     else {
438         /* The user didn't select a hex digit or
439          * text-dump character. */
440         return FALSE;
441     }
442
443     /* Add the number of bytes from the previous rows. */
444     byte += row * 16;
445
446     /* Get the data source tvbuff */
447     tvb = g_object_get_data(G_OBJECT(widget), E_BYTE_VIEW_TVBUFF_KEY);
448
449     return highlight_field(tvb, byte, tree_view, tree);
450 }
451
452 /* This highlights the field in the proto tree that is at position byte */
453 gboolean
454 highlight_field(tvbuff_t *tvb, gint byte, GtkTreeView *tree_view,
455                 proto_tree *tree)
456 {
457     GtkTreeModel *model;
458     GtkTreePath  *first_path, *path;
459     GtkTreeIter   parent;
460     struct field_lookup_info fli;
461     field_info   *finfo;
462
463     /* Find the finfo that corresponds to our byte. */
464     finfo = proto_find_field_from_offset(tree, byte, tvb);
465
466     if (!finfo) {
467         return FALSE;
468     }
469
470     model = gtk_tree_view_get_model(tree_view);
471     fli.fi = finfo;
472     gtk_tree_model_foreach(model, lookup_finfo, &fli);
473
474     /* Expand our field's row */
475     first_path = gtk_tree_model_get_path(model, &fli.iter);
476     gtk_tree_view_expand_row(tree_view, first_path, FALSE);
477     expand_tree(tree_view, &fli.iter, NULL, NULL);
478
479     /* ... and its parents */
480     while (gtk_tree_model_iter_parent(model, &parent, &fli.iter)) {
481         path = gtk_tree_model_get_path(model, &parent);
482         gtk_tree_view_expand_row(tree_view, path, FALSE);
483         expand_tree(tree_view, &parent, NULL, NULL);
484         fli.iter = parent;
485         gtk_tree_path_free(path);
486     }
487
488     /* select our field's row */
489     gtk_tree_selection_select_path(gtk_tree_view_get_selection(tree_view),
490                                    first_path);
491
492     /* And position the window so the selection is visible.
493      * Position the selection in the middle of the viewable
494      * pane. */
495     gtk_tree_view_scroll_to_cell(tree_view, first_path, NULL, TRUE, 0.5f, 0.0f);
496
497     gtk_tree_path_free(first_path);
498
499     return TRUE;
500 }
501
502 /* Calls functions for different mouse-button presses. */
503 static gint
504 byte_view_button_press_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
505 {
506         GdkEventButton *event_button = NULL;
507
508         if(widget == NULL || event == NULL || data == NULL) {
509                 return FALSE;
510         }
511
512         if(event->type == GDK_BUTTON_PRESS) {
513                 event_button = (GdkEventButton *) event;
514
515         /* To qoute the "Gdk Event Structures" doc:
516          * "Normally button 1 is the left mouse button, 2 is the middle button, and 3 is the right button" */
517                 switch(event_button->button) {
518
519                 case 1:
520                         return byte_view_select(widget, event_button);
521                 case 3:
522                         return popup_menu_handler(widget, event, data);
523                 default:
524                         return FALSE;
525                 }
526         }
527
528         return FALSE;
529 }
530
531 GtkWidget *
532 byte_view_new(void)
533 {
534   GtkWidget *byte_nb;
535
536   byte_nb = gtk_notebook_new();
537   gtk_notebook_set_tab_pos(GTK_NOTEBOOK(byte_nb), GTK_POS_BOTTOM);
538
539   /* this will only have an effect, if no tabs are shown */
540   gtk_notebook_set_show_border(GTK_NOTEBOOK(byte_nb), FALSE);
541
542   /* set the tabs scrollable, if they don't fit into the pane */
543   gtk_notebook_set_scrollable(GTK_NOTEBOOK(byte_nb), TRUE);
544
545   /* enable a popup menu containing the tab labels, will be helpful if tabs don't fit into the pane */
546   gtk_notebook_popup_enable(GTK_NOTEBOOK(byte_nb));
547
548   /* Add a placeholder byte view so that there's at least something
549      displayed in the byte view notebook. */
550   add_byte_tab(byte_nb, "", NULL, NULL, NULL);
551
552   return byte_nb;
553 }
554
555 static void
556 byte_view_realize_cb(GtkWidget *bv, gpointer data _U_)
557 {
558     const guint8 *byte_data;
559     guint byte_len;
560
561     byte_data = get_byte_view_data_and_length(bv, &byte_len);
562     if (byte_data == NULL) {
563         /* This must be the dummy byte view if no packet is selected. */
564         return;
565     }
566     packet_hex_print(bv, byte_data, cfile.current_frame, NULL, byte_len);
567 }
568
569 static GtkWidget *
570 add_byte_tab(GtkWidget *byte_nb, const char *name, tvbuff_t *tvb,
571              proto_tree *tree, GtkWidget *tree_view)
572 {
573   GtkWidget *byte_view, *byte_scrollw, *label;
574   GtkTextBuffer *buf;
575   GtkStyle      *style;
576
577   /* Byte view.  Create a scrolled window for the text. */
578   byte_scrollw = scrolled_window_new(NULL, NULL);
579   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(byte_scrollw),
580                                    GTK_SHADOW_IN);
581   /* Add scrolled pane to tabbed window */
582   label = gtk_label_new(name);
583   gtk_notebook_append_page(GTK_NOTEBOOK(byte_nb), byte_scrollw, label);
584
585   gtk_widget_show(byte_scrollw);
586
587   byte_view = gtk_text_view_new();
588   gtk_text_view_set_editable(GTK_TEXT_VIEW(byte_view), FALSE);
589   gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(byte_view), FALSE);
590   buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(byte_view));
591   style = gtk_widget_get_style(GTK_WIDGET(packet_list));
592   gtk_text_buffer_create_tag(buf, "plain", "font-desc", user_font_get_regular(), NULL);
593   gtk_text_buffer_create_tag(buf, "reverse",
594                              "font-desc", user_font_get_regular(),
595                              "foreground-gdk", &style->text[GTK_STATE_SELECTED],
596                              "background-gdk", &style->base[GTK_STATE_SELECTED],
597                              NULL);
598   gtk_text_buffer_create_tag(buf, "bold", "font-desc", user_font_get_bold(), NULL);
599   g_object_set_data(G_OBJECT(byte_view), E_BYTE_VIEW_TVBUFF_KEY, tvb);
600   gtk_container_add(GTK_CONTAINER(byte_scrollw), byte_view);
601
602   g_signal_connect(byte_view, "show", G_CALLBACK(byte_view_realize_cb), NULL);
603   g_signal_connect(byte_view, "button_press_event", G_CALLBACK(byte_view_button_press_cb),
604                  g_object_get_data(G_OBJECT(popup_menu_object), PM_HEXDUMP_KEY));
605
606   g_object_set_data(G_OBJECT(byte_view), E_BYTE_VIEW_TREE_PTR, tree);
607   g_object_set_data(G_OBJECT(byte_view), E_BYTE_VIEW_TREE_VIEW_PTR, tree_view);
608
609   gtk_widget_show(byte_view);
610
611   /* no tabs if this is the first page */
612   if (!(gtk_notebook_page_num(GTK_NOTEBOOK(byte_nb), byte_scrollw)))
613         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(byte_nb), FALSE);
614   else
615         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(byte_nb), TRUE);
616
617   /* set this page (this will print the packet data) */
618   gtk_notebook_set_current_page(GTK_NOTEBOOK(byte_nb),
619     gtk_notebook_page_num(GTK_NOTEBOOK(byte_nb), byte_nb));
620
621   return byte_view;
622 }
623
624 void
625 add_main_byte_views(epan_dissect_t *edt)
626 {
627     add_byte_views(edt, tree_view, byte_nb_ptr);
628 }
629
630 void
631 add_byte_views(epan_dissect_t *edt, GtkWidget *tree_view,
632                GtkWidget *byte_nb_ptr)
633 {
634         GSList *src_le;
635         data_source *src;
636
637         /*
638          * Get rid of all the old notebook tabs.
639          */
640         while (gtk_notebook_get_nth_page(GTK_NOTEBOOK(byte_nb_ptr), 0) != NULL)
641                 gtk_notebook_remove_page(GTK_NOTEBOOK(byte_nb_ptr), 0);
642
643         /*
644          * Add to the specified byte view notebook tabs for hex dumps
645          * of all the data sources for the specified frame.
646          */
647         for (src_le = edt->pi.data_src; src_le != NULL; src_le = src_le->next) {
648                 src = src_le->data;
649                 add_byte_tab(byte_nb_ptr, src->name, src->tvb, edt->tree,
650                              tree_view);
651         }
652
653         /*
654          * Initially select the first byte view.
655          */
656         gtk_notebook_set_current_page(GTK_NOTEBOOK(byte_nb_ptr), 0);
657 }
658
659
660
661 static GtkWidget *savehex_dlg=NULL;
662
663 static void
664 savehex_dlg_destroy_cb(void)
665 {
666         savehex_dlg = NULL;
667 }
668
669
670 static void copy_hex_all_info(GString* copy_buffer, const guint8* data_p, int data_len, gboolean append_text)
671 {
672     const int byte_line_length = 16; /* Print out data for 16 bytes on one line */
673     int i, j;
674     gboolean end_of_line = TRUE; /* Initial state is end of line */
675     int byte_line_part_length;
676
677     GString* hex_str;
678     GString* char_str;
679
680     /* Write hex data for a line, then ascii data, then concatenate and add to buffer */
681     hex_str = g_string_new("");
682     char_str= g_string_new("");
683
684     i = 0;
685         while (i<data_len){
686         if(end_of_line) {
687             g_string_append_printf(hex_str,"%04x  ",i); /* Offset - note that we _append_ here */
688         }
689
690         g_string_append_printf(hex_str," %02x",*data_p);
691         if(append_text) {
692             g_string_append_printf(char_str,"%c",isprint(*data_p) ? *data_p : '.');
693         }
694
695         ++data_p;
696
697         /* Look ahead to see if this is the end of the data */
698         byte_line_part_length = (++i) % byte_line_length;
699         if(i == data_len){
700             /* End of data - need to fill in spaces in hex string and then do "end of line".
701              *
702              */
703             for(j = 0; append_text && (j < (byte_line_length - byte_line_part_length)); ++j) {
704                 g_string_append(hex_str,"   "); /* Three spaces for each missing byte */
705             }
706             end_of_line = TRUE;
707         } else {
708             end_of_line = (byte_line_part_length == 0 ? TRUE : FALSE);
709         }
710
711
712         if (end_of_line){
713             /* End of line */
714             g_string_append(copy_buffer, hex_str->str);
715             if(append_text) {
716                 /* Two spaces between hex and text */
717                 g_string_append_c(copy_buffer, ' ');
718                 g_string_append_c(copy_buffer, ' ');
719                 g_string_append(copy_buffer, char_str->str);
720             }
721             /* Setup ready for next line */
722             g_string_assign(char_str,"");
723             g_string_assign(hex_str, "\n");
724         }
725         }
726
727         g_string_free(hex_str, TRUE);
728         g_string_free(char_str, TRUE);
729 }
730
731 static
732 int copy_hex_bytes_text_only(GString* copy_buffer, const guint8* data_p, int data_len _U_)
733 {
734
735     gchar to_append;
736
737     if(isprint(*data_p)) {
738         to_append = *data_p;
739     } else if(*data_p==0x0a) {
740         to_append = '\n'; /* Copied from old copy_hex_cb function; not sure why this is needed - portablity?*/
741     } else {
742         return 1; /* Just ignore non-printable bytes */
743     }
744     g_string_append_c(copy_buffer,to_append);
745     return 1;
746 }
747
748 static
749 int copy_hex_bytes_hex(GString* copy_buffer, const guint8* data_p, int data_len _U_)
750 {
751     g_string_append_printf(copy_buffer, "%02x", *data_p);
752     return 1;
753 }
754
755 void
756 copy_hex_cb(GtkWidget * w _U_, gpointer data _U_, copy_data_type data_type)
757 {
758         GtkWidget *bv;
759
760     guint len = 0;
761     int bytes_consumed = 0;
762     int flags;
763
764     const guint8* data_p;
765
766         GString* copy_buffer = g_string_new(""); /* String to copy to clipboard */
767
768         bv = get_notebook_bv_ptr(byte_nb_ptr);
769         if (bv == NULL) {
770                 /* shouldn't happen */
771                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Could not find the corresponding text window!");
772                 return;
773         }
774
775         data_p = get_byte_view_data_and_length(bv, &len);
776         g_assert(data_p != NULL);
777
778     flags = data_type & CD_FLAGSMASK;
779     data_type = data_type & CD_TYPEMASK;
780
781     if(flags & CD_FLAGS_SELECTEDONLY) {
782         int start, end;
783
784         /* Get the start and end of the highlighted bytes.
785          * XXX The keys appear to be REVERSED start <-> end throughout this file!
786          * Should this be fixed? There is one exception - packet_hex_reprint,
787          * so can't just change it round without functional change.
788          */
789         end = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_START_KEY));
790         start = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_END_KEY));
791
792         if(start >= 0 && end > start && (end - start <= (int)len)) {
793             len = end - start;
794             data_p += start;
795         }
796     }
797
798     switch(data_type) {
799     case(CD_ALLINFO):
800         /* This is too different from other text formats - handle separately */
801         copy_hex_all_info(copy_buffer, data_p, len, TRUE);
802         break;
803     case(CD_HEXCOLUMNS):
804         /* This could be done incrementally, but it is easier to mingle with the code for CD_ALLINFO */
805         copy_hex_all_info(copy_buffer, data_p, len, FALSE);
806         break;
807     case(CD_BINARY):
808         /* Completely different logic to text copies - leave copy buffer alone */
809         copy_binary_to_clipboard(data_p,len);
810         break;
811     default:
812         /* Incrementally write to text buffer in various formats */
813             while (len > 0){
814             switch(data_type) {
815             case (CD_TEXTONLY):
816                 bytes_consumed = copy_hex_bytes_text_only(copy_buffer, data_p, len);
817                 break;
818             case (CD_HEX):
819                 bytes_consumed = copy_hex_bytes_hex(copy_buffer, data_p, len);
820                 break;
821             default:
822                 g_assert_not_reached();
823                 break;
824             }
825
826             g_assert(bytes_consumed>0);
827             data_p += bytes_consumed;
828             len -= bytes_consumed;
829         }
830         break;
831     }
832
833     if(copy_buffer->len > 0) {
834         copy_to_clipboard(copy_buffer);
835     }
836
837         g_string_free(copy_buffer, TRUE);
838 }
839
840 /* save the current highlighted hex data */
841 static gboolean
842 savehex_save_clicked_cb(GtkWidget * w _U_, gpointer data _U_)
843 {
844         GtkWidget *bv;
845         int fd, start, end;
846         guint len;
847         const guint8 *data_p = NULL;
848         char *file;
849
850         file = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(savehex_dlg));
851
852 #if 0 /* Not req'd: GtkFileChooserWidget currently being used won't return with a Null filename */
853         if (!file ||! *file) {
854                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Please enter a filename!");
855                 g_free(file);
856                 return TRUE;
857         }
858 #endif
859         if (test_for_directory(file) == EISDIR) {
860                 /* It's a directory - set the file selection box to display that
861                    directory, and leave the selection box displayed. */
862                 set_last_open_dir(file);
863                 g_free(file);
864                 file_selection_set_current_folder(savehex_dlg, get_last_open_dir());
865                 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(savehex_dlg), "");
866                 return FALSE; /* do gtk_dialog_run again */
867         }
868
869         /* XXX: Must check if file name exists first */
870
871         bv = get_notebook_bv_ptr(byte_nb_ptr);
872         if (bv == NULL) {
873                 /* shouldn't happen */
874                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Could not find the corresponding text window!");
875                 g_free(file);
876                 return TRUE;
877         }
878         /*
879          * Retrieve the info we need
880          */
881         end = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_START_KEY));
882         start = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_END_KEY));
883         data_p = get_byte_view_data_and_length(bv, &len);
884
885         if (data_p == NULL || start == -1 || start > end) {
886                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
887                     "No data selected to save!");
888                 g_free(file);
889                 return TRUE;
890         }
891
892         fd = ws_open(file, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
893         if (fd == -1) {
894                 open_failure_alert_box(file, errno, TRUE);
895                 g_free(file);
896                 return TRUE;
897         }
898         if (ws_write(fd, data_p + start, end - start) < 0) {
899                 write_failure_alert_box(file, errno);
900                 ws_close(fd);
901                 g_free(file);
902                 return TRUE;
903         }
904         if (ws_close(fd) < 0) {
905                 write_failure_alert_box(file, errno);
906                 g_free(file);
907                 return TRUE;
908         }
909
910         /* Get rid of the dialog box */
911         g_free(file);
912 #if 0 /* being handled by caller  (for now) */
913         window_destroy(GTK_WIDGET(savehex_dlg));
914 #endif
915         return TRUE;
916 }
917
918 /* Launch the dialog box to put up the file selection box etc */
919 void savehex_cb(GtkWidget * w _U_, gpointer data _U_)
920 {
921         int start, end;
922         guint len;
923         const guint8 *data_p = NULL;
924         gchar *label;
925
926         GtkWidget   *bv;
927         GtkWidget   *dlg_lb;
928
929 #if _WIN32
930         win32_export_raw_file(GDK_WINDOW_HWND(top_level->window));
931         return;
932 #endif
933
934     /* don't show up the dialog, if no data has to be saved */
935         bv = get_notebook_bv_ptr(byte_nb_ptr);
936         if (bv == NULL) {
937                 /* shouldn't happen */
938                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Could not find the corresponding text window!");
939                 return;
940         }
941         end = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_START_KEY));
942         start = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_END_KEY));
943         data_p = get_byte_view_data_and_length(bv, &len);
944
945         if (data_p == NULL || start == -1 || start > end) {
946                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "No data selected to save!");
947                 return;
948         }
949
950 #if 0  /* XXX: GtkFileChooserDialog/gtk_dialog_run currently being used is effectively modal so this is not req'd */
951     /* if the window is already open, bring it to front */
952         if(savehex_dlg){
953                 reactivate_window(savehex_dlg);
954                 return;
955         }
956 #endif
957         /*
958          * Build the dialog box we need.
959          */
960     savehex_dlg = file_selection_new("Wireshark: Export Selected Packet Bytes", FILE_SELECTION_SAVE);
961 #if GTK_CHECK_VERSION(2,8,0)
962     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(savehex_dlg), TRUE);
963 #endif
964
965     /* label */
966     label = g_strdup_printf("Will save %u %s of raw binary data to specified file.",
967                             end - start, plurality(end - start, "byte", "bytes"));
968     dlg_lb = gtk_label_new(label);
969     g_free(label);
970     file_selection_set_extra_widget(savehex_dlg, dlg_lb);
971     gtk_widget_show(dlg_lb);
972
973     g_signal_connect(savehex_dlg, "destroy", G_CALLBACK(savehex_dlg_destroy_cb), NULL);
974
975 #if 0
976     if (gtk_dialog_run(GTK_DIALOG(savehex_dlg)) == GTK_RESPONSE_ACCEPT) {
977         savehex_save_clicked_cb(savehex_dlg, savehex_dlg);
978     } else {
979         window_destroy(savehex_dlg);
980     }
981 #endif
982     /* "Run" the GtkFileChooserDialog.                                              */
983     /* Upon exit: If "Accept" run the OK callback.                                  */
984     /*            If the OK callback returns with a FALSE status, re-run the dialog.*/
985     /*            If not accept (ie: cancel) destroy the window.                    */
986     /* XXX: If the OK callback pops up an alert box (eg: for an error) it *must*    */
987     /*      return with a TRUE status so that the dialog window will be destroyed.  */
988     /*      Trying to re-run the dialog after popping up an alert box will not work */
989     /*       since the user will not be able to dismiss the alert box.              */
990     /*      The (somewhat unfriendly) effect: the user must re-invoke the           */
991     /*      GtkFileChooserDialog whenever the OK callback pops up an alert box.     */
992     /*                                                                              */
993     /*      ToDo: use GtkFileChooserWidget in a dialog window instead of            */
994     /*            GtkFileChooserDialog.                                             */
995     while (gtk_dialog_run(GTK_DIALOG(savehex_dlg)) == GTK_RESPONSE_ACCEPT) {
996         if (savehex_save_clicked_cb(NULL, savehex_dlg)) {
997             break; /* we're done */
998         }
999     }
1000     window_destroy(savehex_dlg);
1001 }
1002
1003
1004
1005 /* Update the progress bar this many times when reading a file. */
1006 #define N_PROGBAR_UPDATES       100
1007
1008
1009 /*
1010  * XXX - at least in GTK+ 2.x, this is not fast - in one capture with a
1011  * 64K-or-so reassembled HTTP reply, it takes about 3 seconds to construct
1012  * the hex dump pane on a 1.4 GHz G4 PowerMac on OS X 10.3.3.  (That's
1013  * presumably why there's a progress bar for it.)
1014  *
1015  * Perhaps what's needed is a custom widget (either one that lets you stuff
1016  * text into it more quickly, or one that's a "virtual" widget so that the
1017  * text for a row is constructed, via a callback, when the row is to be
1018  * displayed).  A custom widget might also let us treat the offset, hex
1019  * data, and ASCII data as three columns, so you can select purely in
1020  * the hex dump column.
1021  */
1022 static void
1023 packet_hex_print_common(GtkWidget *bv, const guint8 *pd, int len, int bstart,
1024                         int bend, int astart, int aend, int encoding)
1025 {
1026   int            i = 0, j, k, cur;
1027   guchar         line[MAX_LINES_LEN + 1];
1028   static guchar  hexchars[16] = {
1029       '0', '1', '2', '3', '4', '5', '6', '7',
1030       '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
1031   guchar         c = '\0';
1032   unsigned int   use_digits;
1033   gboolean       reverse, newreverse;
1034   GtkTextView   *bv_text_view = GTK_TEXT_VIEW(bv);
1035   GtkTextBuffer *buf = gtk_text_view_get_buffer(bv_text_view);
1036   GtkTextIter    iter;
1037   const char    *revstyle;
1038   GtkTextMark   *mark = NULL;
1039
1040   progdlg_t  *progbar = NULL;
1041   float       progbar_val;
1042   gboolean    progbar_stop_flag;
1043   GTimeVal    progbar_start_time;
1044   gchar       progbar_status_str[100];
1045   int         progbar_nextstep;
1046   int         progbar_quantum;
1047
1048   gtk_text_buffer_set_text(buf, "", 0);
1049   gtk_text_buffer_get_start_iter(buf, &iter);
1050   g_object_ref(buf);
1051   gtk_text_view_set_buffer( bv_text_view, NULL);
1052
1053   /*
1054    * How many of the leading digits of the offset will we supply?
1055    * We always supply at least 4 digits, but if the maximum offset
1056    * won't fit in 4 digits, we use as many digits as will be needed.
1057    */
1058   if (((len - 1) & 0xF0000000) != 0)
1059     use_digits = 8;     /* need all 8 digits */
1060   else if (((len - 1) & 0x0F000000) != 0)
1061     use_digits = 7;     /* need 7 digits */
1062   else if (((len - 1) & 0x00F00000) != 0)
1063     use_digits = 6;     /* need 6 digits */
1064   else if (((len - 1) & 0x000F0000) != 0)
1065     use_digits = 5;     /* need 5 digits */
1066   else
1067     use_digits = 4;     /* we'll supply 4 digits */
1068
1069   /* Record the number of digits in this text view. */
1070   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_NDIGITS_KEY, GUINT_TO_POINTER(use_digits));
1071
1072   /* Update the progress bar when it gets to this value. */
1073   progbar_nextstep = 0;
1074   /* When we reach the value that triggers a progress bar update,
1075      bump that value by this amount. */
1076   progbar_quantum = len/N_PROGBAR_UPDATES;
1077   /* Progress so far. */
1078   progbar_val = 0.0f;
1079
1080   progbar_stop_flag = FALSE;
1081   g_get_current_time(&progbar_start_time);
1082
1083   cur = 0;
1084   while (i < len) {
1085     /* Create the progress bar if necessary.
1086        We check on every iteration of the loop, so that it takes no
1087        longer than the standard time to create it (otherwise, for a
1088        large packet, we might take considerably longer than that standard
1089        time in order to get to the next progress bar step). */
1090     if (progbar == NULL)
1091       progbar = delayed_create_progress_dlg("Processing", "Packet Details",
1092                                             TRUE,
1093                                             &progbar_stop_flag,
1094                                             &progbar_start_time,
1095                                             progbar_val);
1096
1097     /* Update the progress bar, but do it only N_PROGBAR_UPDATES times;
1098        when we update it, we have to run the GTK+ main loop to get it
1099        to repaint what's pending, and doing so may involve an "ioctl()"
1100        to see if there's any pending input from an X server, and doing
1101        that for every packet can be costly, especially on a big file. */
1102     if (i >= progbar_nextstep) {
1103       /* let's not divide by zero. I should never be started
1104        * with count == 0, so let's assert that
1105        */
1106       g_assert(len > 0);
1107       progbar_val = (gfloat) i / len;
1108
1109       if (progbar != NULL) {
1110         g_snprintf(progbar_status_str, sizeof(progbar_status_str),
1111                    "%4u of %u bytes", i, len);
1112         update_progress_dlg(progbar, progbar_val, progbar_status_str);
1113       }
1114
1115       progbar_nextstep += progbar_quantum;
1116     }
1117
1118     if (progbar_stop_flag) {
1119       /* Well, the user decided to abort the operation.  Just stop,
1120          and arrange to return TRUE to our caller, so they know it
1121          was stopped explicitly. */
1122       break;
1123     }
1124
1125     /* Print the line number */
1126     j = use_digits;
1127     do {
1128       j--;
1129       c = (i >> (j*4)) & 0xF;
1130       line[cur++] = hexchars[c];
1131     } while (j != 0);
1132     line[cur++] = ' ';
1133     line[cur++] = ' ';
1134
1135     /* Display with inverse video ? */
1136     if (prefs.gui_hex_dump_highlight_style)
1137       revstyle = "reverse";
1138     else
1139       revstyle = "bold";
1140
1141     /* Do we start in reverse? */
1142     reverse = (i >= bstart && i < bend) || (i >= astart && i < aend);
1143     j   = i;
1144     k   = i + BYTE_VIEW_WIDTH;
1145     if (reverse) {
1146       gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1147                                              "plain", NULL);
1148       cur = 0;
1149     }
1150     /* Print the hex bit */
1151     while (i < k) {
1152       if (i < len) {
1153         line[cur++] = hexchars[(pd[i] & 0xf0) >> 4];
1154         line[cur++] = hexchars[pd[i] & 0x0f];
1155       } else {
1156         line[cur++] = ' '; line[cur++] = ' ';
1157       }
1158       i++;
1159       newreverse = (i >= bstart && i < bend) || (i >= astart && i < aend);
1160       /* Have we gone from reverse to plain? */
1161       if (reverse && (reverse != newreverse)) {
1162         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1163                                                  revstyle, NULL);
1164         cur = 0;
1165       }
1166       /* Inter byte space if not at end of line */
1167       if (i < k) {
1168         line[cur++] = ' ';
1169         /* insert a space every BYTE_VIEW_SEP bytes */
1170         if( ( i % BYTE_VIEW_SEP ) == 0 ) {
1171           line[cur++] = ' ';
1172         }
1173       }
1174       /* Have we gone from plain to reversed? */
1175       if (!reverse && (reverse != newreverse)) {
1176         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1177                                                  "plain", NULL);
1178         mark = gtk_text_buffer_create_mark(buf, NULL, &iter, TRUE);
1179         cur = 0;
1180       }
1181       reverse = newreverse;
1182     }
1183     if (reverse) {
1184       /* Print remaining part of line */
1185       gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1186                                              revstyle, NULL);
1187       cur = 0;
1188     }
1189
1190     /* Print some space at the end of the line */
1191     line[cur++] = ' '; line[cur++] = ' '; line[cur++] = ' ';
1192
1193     /* Print the ASCII bit */
1194     i = j;
1195     /* Do we start in reverse? */
1196     reverse = (i >= bstart && i < bend) || (i >= astart && i < aend);
1197     if (reverse) {
1198       gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1199                                              "plain",NULL);
1200       cur = 0;
1201     }
1202
1203     while (i < k) {
1204       if (i < len) {
1205         if (encoding == CHAR_ASCII) {
1206           c = pd[i];
1207         }
1208         else if (encoding == CHAR_EBCDIC) {
1209           c = EBCDIC_to_ASCII1(pd[i]);
1210         }
1211         else {
1212           g_assert_not_reached();
1213         }
1214         line[cur++] = isprint(c) ? c : '.';
1215       } else {
1216         line[cur++] = ' ';
1217       }
1218       i++;
1219       newreverse = (i >= bstart && i < bend) || (i >= astart && i < aend);
1220       /* Have we gone from reverse to plain? */
1221       if (reverse && (reverse != newreverse)) {
1222         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1223                                                  revstyle, NULL);
1224
1225         cur = 0;
1226       }
1227       if (i < k) {
1228         /* insert a space every BYTE_VIEW_SEP bytes */
1229         if( ( i % BYTE_VIEW_SEP ) == 0 ) {
1230           line[cur++] = ' ';
1231         }
1232       }
1233       /* Have we gone from plain to reversed? */
1234       if (!reverse && (reverse != newreverse)) {
1235         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1236                                                  "plain", NULL);
1237         cur = 0;
1238       }
1239       reverse = newreverse;
1240     }
1241     /* Print remaining part of line */
1242     if (reverse) {
1243         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1244                                              revstyle, NULL);
1245         cur = 0;
1246     }
1247     line[cur++] = '\n';
1248     if (cur >= (MAX_LINES_LEN - MAX_LINE_LEN)) {
1249         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1250                                              "plain", NULL);
1251         cur = 0;
1252     }
1253   }
1254
1255   /* We're done printing the packets; destroy the progress bar if
1256      it was created. */
1257   if (progbar != NULL)
1258     destroy_progress_dlg(progbar);
1259
1260   /* scroll text into position */
1261   if (cur) {
1262         gtk_text_buffer_insert_with_tags_by_name(buf, &iter, line, cur,
1263                                              "plain", NULL);
1264   }
1265   gtk_text_view_set_buffer( bv_text_view, buf);
1266
1267   if (mark) {
1268     gtk_text_view_scroll_to_mark(bv_text_view, mark, 0.0, TRUE, 1.0, 0.0);
1269     gtk_text_buffer_delete_mark(buf, mark);
1270   }
1271   g_object_unref(buf);
1272 }
1273
1274 void
1275 packet_hex_print(GtkWidget *bv, const guint8 *pd, frame_data *fd,
1276                  field_info *finfo, guint len)
1277 {
1278   /* do the initial printing and save the information needed    */
1279   /* to redraw the display if preferences change.               */
1280
1281   int bstart = -1, bend = -1, blen = -1;
1282   int astart = -1, aend = -1, alen = -1;
1283
1284   if (finfo != NULL) {
1285     bstart = finfo->start;
1286     blen = finfo->length;
1287     astart = finfo->appendix_start;
1288     alen = finfo->appendix_length;
1289   }
1290   if (bstart >= 0 && blen >= 0) {
1291     bend = bstart + blen;
1292   }
1293   if (astart >= 0 && alen >= 0) {
1294     aend = astart + alen;
1295   }
1296
1297   /* save the information needed to redraw the text */
1298   /* should we save the fd & finfo pointers instead ?? */
1299   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_START_KEY, GINT_TO_POINTER(bend));
1300   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_END_KEY, GINT_TO_POINTER(bstart));
1301   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_APP_START_KEY, GINT_TO_POINTER(aend));
1302   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_APP_END_KEY, GINT_TO_POINTER(astart));
1303   g_object_set_data(G_OBJECT(bv), E_BYTE_VIEW_ENCODE_KEY,
1304                   GUINT_TO_POINTER((guint)fd->flags.encoding));
1305
1306   packet_hex_print_common(bv, pd, len, bstart, bend, astart, aend, fd->flags.encoding);
1307 }
1308
1309 /*
1310  * Redraw the text using the saved information; usually called if
1311  * the preferences have changed.
1312  */
1313 void
1314 packet_hex_reprint(GtkWidget *bv)
1315 {
1316   int start, end, encoding;
1317   int astart, aend;
1318   const guint8 *data;
1319   guint len = 0;
1320
1321   start = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_START_KEY));
1322   end = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_END_KEY));
1323   astart = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_APP_START_KEY));
1324   aend = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_APP_END_KEY));
1325   data = get_byte_view_data_and_length(bv, &len);
1326   g_assert(data != NULL);
1327   encoding = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(bv), E_BYTE_VIEW_ENCODE_KEY));
1328
1329   packet_hex_print_common(bv, data, len, start, end, astart, aend, encoding);
1330 }
1331
1332 /* List of all protocol tree widgets, so we can globally set the selection
1333    mode and font of all of them. */
1334 static GList *ptree_widgets;
1335
1336 /* Add a protocol tree widget to the list of protocol tree widgets. */
1337 static void forget_ptree_widget(GtkWidget *ptreew, gpointer data);
1338
1339 static void
1340 remember_ptree_widget(GtkWidget *ptreew)
1341 {
1342   ptree_widgets = g_list_append(ptree_widgets, ptreew);
1343
1344   /* Catch the "destroy" event on the widget, so that we remove it from
1345      the list when it's destroyed. */
1346   g_signal_connect(ptreew, "destroy", G_CALLBACK(forget_ptree_widget), NULL);
1347 }
1348
1349 /* Remove a protocol tree widget from the list of protocol tree widgets. */
1350 static void
1351 forget_ptree_widget(GtkWidget *ptreew, gpointer data _U_)
1352 {
1353   ptree_widgets = g_list_remove(ptree_widgets, ptreew);
1354 }
1355
1356 /* Set the selection mode of a given packet tree window. */
1357 static void
1358 set_ptree_sel_browse(GtkWidget *tree, gboolean val)
1359 {
1360     GtkTreeSelection *selection;
1361
1362     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
1363     /* Yeah, GTK uses "browse" in the case where we do not, but oh well.
1364        I think "browse" in Wireshark makes more sense than "SINGLE" in
1365        GTK+ */
1366     if (val) {
1367         gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
1368     }
1369     else {
1370         gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1371     }
1372 }
1373
1374 static void
1375 set_ptree_sel_browse_cb(gpointer data, gpointer user_data)
1376 {
1377         set_ptree_sel_browse((GtkWidget *)data, *(gboolean *)user_data);
1378 }
1379
1380 /* Set the selection mode of all packet tree windows. */
1381 void
1382 set_ptree_sel_browse_all(gboolean val)
1383 {
1384         g_list_foreach(ptree_widgets, set_ptree_sel_browse_cb, &val);
1385 }
1386
1387 static void
1388 set_ptree_font_cb(gpointer data, gpointer user_data)
1389 {
1390         gtk_widget_modify_font((GtkWidget *)data,
1391                                (PangoFontDescription *)user_data);
1392 }
1393
1394 void
1395 set_ptree_font_all(PangoFontDescription *font)
1396 {
1397     g_list_foreach(ptree_widgets, set_ptree_font_cb, font);
1398 }
1399
1400
1401 gboolean colors_ok = FALSE;
1402 GdkColor        expert_color_chat       = { 0, 0xcc00, 0xcc00, 0xe000 };        /* a pale bluegrey */
1403 GdkColor        expert_color_note       = { 0, 0xa000, 0xff00, 0xff00 };        /* a bright turquoise */
1404 GdkColor        expert_color_warn       = { 0, 0xff00, 0xff00, 0 };                     /* yellow */
1405 GdkColor        expert_color_error      = { 0, 0xff00, 0x5c00, 0x5c00 };        /* pale red */
1406 GdkColor        expert_color_foreground = { 0, 0x0000, 0x0000, 0x0000 };        /* black */
1407 GdkColor        hidden_proto_item       = { 0, 0x4400, 0x4400, 0x4400 };        /* gray */
1408
1409 void proto_draw_colors_init(void)
1410 {
1411         if(colors_ok) {
1412                 return;
1413         }
1414
1415         get_color(&expert_color_chat);
1416         get_color(&expert_color_note);
1417         get_color(&expert_color_warn);
1418         get_color(&expert_color_error);
1419         get_color(&expert_color_foreground);
1420         get_color(&hidden_proto_item);
1421
1422         colors_ok = TRUE;
1423 }
1424
1425
1426 static void tree_cell_renderer(GtkTreeViewColumn *tree_column _U_,
1427                                              GtkCellRenderer *cell,
1428                                              GtkTreeModel *tree_model,
1429                                              GtkTreeIter *iter,
1430                                              gpointer data _U_)
1431 {
1432     field_info   *fi;
1433
1434     gtk_tree_model_get(tree_model, iter, 1, &fi, -1);
1435
1436         if(!colors_ok) {
1437                 proto_draw_colors_init();
1438         }
1439
1440         /* for the various possible attributes, see:
1441          * http://developer.gnome.org/doc/API/2.0/gtk/GtkCellRendererText.html
1442          *
1443          * color definitions can be found at:
1444          * http://cvs.gnome.org/viewcvs/gtk+/gdk-pixbuf/io-xpm.c?rev=1.42
1445          * (a good color overview: http://www.computerhope.com/htmcolor.htm)
1446          *
1447          * some experiences:
1448          * background-gdk: doesn't seem to work (probably the GdkColor must be allocated)
1449          * weight/style: doesn't take any effect
1450          */
1451
1452     /* for each field, we have to reset the renderer attributes */
1453     g_object_set (cell, "foreground-set", FALSE, NULL);
1454
1455     g_object_set (cell, "background-set", FALSE, NULL);
1456
1457     g_object_set (cell, "underline", PANGO_UNDERLINE_NONE, NULL);
1458     g_object_set (cell, "underline-set", FALSE, NULL);
1459
1460     /*g_object_set (cell, "style", PANGO_STYLE_NORMAL, NULL);
1461     g_object_set (cell, "style-set", FALSE, NULL);*/
1462
1463     /*g_object_set (cell, "weight", PANGO_WEIGHT_NORMAL, NULL);
1464     g_object_set (cell, "weight-set", FALSE, NULL);*/
1465
1466     if(FI_GET_FLAG(fi, FI_GENERATED)) {
1467                 /* we use "[...]" to mark generated items, no need to change things here */
1468
1469         /* as some fonts don't support italic, don't use this */
1470         /*g_object_set (cell, "style", PANGO_STYLE_ITALIC, NULL);
1471         g_object_set (cell, "style-set", TRUE, NULL);
1472         */
1473         /*g_object_set (cell, "weight", PANGO_WEIGHT_BOLD, NULL);
1474         g_object_set (cell, "weight-set", TRUE, NULL);*/
1475     }
1476
1477     if(FI_GET_FLAG(fi, FI_HIDDEN)) {
1478         g_object_set (cell, "foreground-gdk", &hidden_proto_item, NULL);
1479         g_object_set (cell, "foreground-set", TRUE, NULL);
1480     }
1481
1482     if(fi->hfinfo->type == FT_PROTOCOL) {
1483         g_object_set (cell, "background", "gray90", NULL);
1484         g_object_set (cell, "background-set", TRUE, NULL);
1485         g_object_set (cell, "foreground", "black", NULL);
1486         g_object_set (cell, "foreground-set", TRUE, NULL);
1487         /*g_object_set (cell, "weight", PANGO_WEIGHT_BOLD, NULL);
1488         g_object_set (cell, "weight-set", TRUE, NULL);*/
1489         }
1490
1491     if((fi->hfinfo->type == FT_FRAMENUM) ||
1492        (FI_GET_FLAG(fi, FI_URL) && IS_FT_STRING(fi->hfinfo->type))) {
1493         render_as_url(cell);
1494     }
1495
1496         if(FI_GET_FLAG(fi, PI_SEVERITY_MASK)) {
1497                 switch(FI_GET_FLAG(fi, PI_SEVERITY_MASK)) {
1498                 case(PI_CHAT):
1499                         g_object_set (cell, "background-gdk", &expert_color_chat, NULL);
1500                         g_object_set (cell, "background-set", TRUE, NULL);
1501                         break;
1502                 case(PI_NOTE):
1503                         g_object_set (cell, "background-gdk", &expert_color_note, NULL);
1504                         g_object_set (cell, "background-set", TRUE, NULL);
1505                         break;
1506                 case(PI_WARN):
1507                         g_object_set (cell, "background-gdk", &expert_color_warn, NULL);
1508                         g_object_set (cell, "background-set", TRUE, NULL);
1509                         break;
1510                 case(PI_ERROR):
1511                         g_object_set (cell, "background-gdk", &expert_color_error, NULL);
1512                         g_object_set (cell, "background-set", TRUE, NULL);
1513                         break;
1514                 default:
1515                         g_assert_not_reached();
1516                 }
1517                 g_object_set (cell, "foreground", "black", NULL);
1518                 g_object_set (cell, "foreground-set", TRUE, NULL);
1519         }
1520 }
1521
1522 GtkWidget *
1523 main_tree_view_new(e_prefs *prefs, GtkWidget **tree_view_p)
1524 {
1525   GtkWidget *tv_scrollw, *tree_view;
1526   GtkTreeStore *store;
1527   GtkCellRenderer *renderer;
1528   GtkTreeViewColumn *column;
1529   gint col_offset;
1530
1531   /* Tree view */
1532   tv_scrollw = scrolled_window_new(NULL, NULL);
1533   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(tv_scrollw),
1534                                    GTK_SHADOW_IN);
1535
1536   store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
1537   tree_view = tree_view_new(GTK_TREE_MODEL(store));
1538   g_object_unref(G_OBJECT(store));
1539   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
1540   renderer = gtk_cell_renderer_text_new();
1541   g_object_set (renderer, "ypad", 0, NULL);
1542   col_offset = gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree_view),
1543                                                            -1, "Name", renderer,
1544                                                            "text", 0, NULL);
1545   column = gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view),
1546                                     col_offset - 1);
1547   gtk_tree_view_column_set_cell_data_func(column,
1548                                              renderer,
1549                                              tree_cell_renderer,
1550                                              NULL,
1551                                              NULL);
1552
1553   gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
1554                                   GTK_TREE_VIEW_COLUMN_AUTOSIZE);
1555   g_signal_connect(tree_view, "row-expanded", G_CALLBACK(expand_tree), NULL);
1556   g_signal_connect(tree_view, "row-collapsed", G_CALLBACK(collapse_tree), NULL);
1557   gtk_container_add( GTK_CONTAINER(tv_scrollw), tree_view );
1558   set_ptree_sel_browse(tree_view, prefs->gui_ptree_sel_browse);
1559   gtk_widget_modify_font(tree_view, user_font_get_regular());
1560   remember_ptree_widget(tree_view);
1561
1562   *tree_view_p = tree_view;
1563
1564   return tv_scrollw;
1565 }
1566
1567 void expand_all_tree(proto_tree *protocol_tree _U_, GtkWidget *tree_view)
1568 {
1569   int i;
1570   for(i=0; i < num_tree_types; i++) {
1571     tree_is_expanded[i] = TRUE;
1572   }
1573   gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view));
1574 }
1575
1576 void collapse_all_tree(proto_tree *protocol_tree _U_, GtkWidget *tree_view)
1577 {
1578   int i;
1579   for(i=0; i < num_tree_types; i++) {
1580     tree_is_expanded[i] = FALSE;
1581   }
1582   gtk_tree_view_collapse_all(GTK_TREE_VIEW(tree_view));
1583 }
1584
1585
1586 struct proto_tree_draw_info {
1587     GtkTreeView  *tree_view;
1588     GtkTreeIter  *iter;
1589 };
1590
1591 void
1592 main_proto_tree_draw(proto_tree *protocol_tree)
1593 {
1594     proto_tree_draw(protocol_tree, tree_view);
1595 }
1596
1597
1598 static void
1599 tree_view_follow_link(field_info   *fi)
1600 {
1601     gchar *url;
1602
1603     if(fi->hfinfo->type == FT_FRAMENUM) {
1604         cf_goto_frame(&cfile, fi->value.value.uinteger);
1605     }
1606     if(FI_GET_FLAG(fi, FI_URL) && IS_FT_STRING(fi->hfinfo->type)) {
1607         url = g_strndup(tvb_get_ptr(fi->ds_tvb, fi->start, fi->length), fi->length);
1608         browser_open_url(url);
1609         g_free(url);
1610     }
1611 }
1612
1613
1614 /* If the user selected a position in the tree view, try to find
1615  * the item in the GUI proto_tree that corresponds to that byte, and
1616  * select it. */
1617 gboolean
1618 tree_view_select(GtkWidget *widget, GdkEventButton *event)
1619 {
1620         GtkTreeSelection    *sel;
1621         GtkTreePath         *path;
1622
1623         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
1624                                           (gint) (((GdkEventButton *)event)->x),
1625                                           (gint) (((GdkEventButton *)event)->y),
1626                                           &path, NULL, NULL, NULL))
1627         {
1628             sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1629
1630             /* if that's a doubleclick, try to follow the link */
1631             if(event->type == GDK_2BUTTON_PRESS) {
1632                 GtkTreeModel *model;
1633                 GtkTreeIter iter;
1634                 field_info   *fi;
1635
1636                 if(gtk_tree_selection_get_selected (sel, &model, &iter)) {
1637                     gtk_tree_model_get(model, &iter, 1, &fi, -1);
1638                     tree_view_follow_link(fi);
1639                 }
1640             }
1641             else if (((GdkEventButton *)event)->button != 1) {
1642                 /* if button == 1 gtk_tree_selection_select_path is already (or will be) called by the widget */
1643                 gtk_tree_selection_select_path(sel, path);
1644             }
1645         } else {
1646             return FALSE;
1647         }
1648     return TRUE;
1649 }
1650
1651 /* fill the whole protocol tree with the string values */
1652 void
1653 proto_tree_draw(proto_tree *protocol_tree, GtkWidget *tree_view)
1654 {
1655     GtkTreeStore *store;
1656     struct proto_tree_draw_info info;
1657
1658     info.tree_view = GTK_TREE_VIEW(tree_view);
1659     info.iter = NULL;
1660
1661     store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view)));
1662
1663     /*
1664      * Clear out any crud left over in the display of the protocol
1665      * tree, by removing all nodes from the tree.
1666      * This is how it's done in testgtk.c in GTK+.
1667      */
1668     gtk_tree_store_clear(store);
1669
1670     proto_tree_children_foreach(protocol_tree, proto_tree_draw_node, &info);
1671 }
1672
1673
1674 /* fill a single protocol tree item with the string value */
1675 static void
1676 proto_tree_draw_node(proto_node *node, gpointer data)
1677 {
1678     struct proto_tree_draw_info info;
1679     struct proto_tree_draw_info *parent_info = (struct proto_tree_draw_info*) data;
1680
1681     field_info   *fi = PITEM_FINFO(node);
1682     gchar         label_str[ITEM_LABEL_LENGTH];
1683     gchar        *label_ptr;
1684     gboolean      is_leaf, is_expanded;
1685     GtkTreeStore *store;
1686     GtkTreeIter   iter;
1687     GtkTreePath  *path;
1688
1689     if (PROTO_ITEM_IS_HIDDEN(node) && !prefs.display_hidden_proto_items)
1690         return;
1691
1692     /* was a free format label produced? */
1693     if (fi->rep) {
1694         label_ptr = fi->rep->representation;
1695     }
1696     else { /* no, make a generic label */
1697         label_ptr = label_str;
1698         proto_item_fill_label(fi, label_str);
1699     }
1700
1701     if (node->first_child != NULL) {
1702         is_leaf = FALSE;
1703         g_assert(fi->tree_type >= 0 && fi->tree_type < num_tree_types);
1704         if (tree_is_expanded[fi->tree_type]) {
1705             is_expanded = TRUE;
1706         }
1707         else {
1708             is_expanded = FALSE;
1709         }
1710     }
1711     else {
1712         is_leaf = TRUE;
1713         is_expanded = FALSE;
1714     }
1715
1716     if (PROTO_ITEM_IS_GENERATED(node)) {
1717         if (PROTO_ITEM_IS_HIDDEN(node)) {
1718             label_ptr = g_strdup_printf("<[%s]>", label_ptr);
1719         } else {
1720             label_ptr = g_strdup_printf("[%s]", label_ptr);
1721         }
1722     } else if (PROTO_ITEM_IS_HIDDEN(node)) {
1723         label_ptr = g_strdup_printf("<%s>", label_ptr);
1724     }
1725
1726     info.tree_view = parent_info->tree_view;
1727     store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(info.tree_view)));
1728     gtk_tree_store_append(store, &iter, parent_info->iter);
1729     gtk_tree_store_set(store, &iter, 0, label_ptr, 1, fi, -1);
1730
1731     if (PROTO_ITEM_IS_GENERATED(node) || PROTO_ITEM_IS_HIDDEN(node)) {
1732         g_free(label_ptr);
1733     }
1734
1735     if (!is_leaf) {
1736         info.iter = &iter;
1737         proto_tree_children_foreach(node, proto_tree_draw_node, &info);
1738         path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
1739         if (is_expanded)
1740             gtk_tree_view_expand_to_path(info.tree_view, path);
1741         else
1742             gtk_tree_view_collapse_row(info.tree_view, path);
1743         gtk_tree_path_free(path);
1744     }
1745 }
1746
1747 /*
1748  * Clear the hex dump and protocol tree panes.
1749  */
1750 void
1751 clear_tree_and_hex_views(void)
1752 {
1753   /* Clear the hex dump by getting rid of all the byte views. */
1754   while (gtk_notebook_get_nth_page(GTK_NOTEBOOK(byte_nb_ptr), 0) != NULL)
1755     gtk_notebook_remove_page(GTK_NOTEBOOK(byte_nb_ptr), 0);
1756
1757   /* Add a placeholder byte view so that there's at least something
1758      displayed in the byte view notebook. */
1759   add_byte_tab(byte_nb_ptr, "", NULL, NULL, tree_view);
1760
1761   /* Clear the protocol tree by removing all nodes in the ctree.
1762      This is how it's done in testgtk.c in GTK+ */
1763   gtk_tree_store_clear(GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view))));
1764 }
1765