Workaround for bug 852. Force a line break so that the text view doesn't blow up...
[obnox/wireshark/wip.git] / gtk / follow_dlg.c
index 23115eae36bbf71836dc34139fcb6f8ed64f0086..cb3d0c36b62598c4a5c1c744b8a53eae7ff68ca1 100644 (file)
@@ -1,25 +1,24 @@
 /* follow_dlg.c
  *
- * $Id: follow_dlg.c,v 1.1 2000/08/03 12:44:36 gram Exp $
+ * $Id$
  *
  * Ethereal - Network traffic analyzer
- * By Gerald Combs <gerald@zing.org>
+ * By Gerald Combs <gerald@ethereal.com>
  * Copyright 2000 Gerald Combs
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
- *
  */
 
 #ifdef HAVE_CONFIG_H
 
 #include <gtk/gtk.h>
 
-#include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
 
-#ifdef HAVE_IO_H
-#include <io.h> /* open/close on win32 */
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
 #endif
 
+#include <ctype.h>
+
+#include "isprint.h"
+
+#include "file_util.h"
+#include "color.h"
+#include "colors.h"
 #include "file.h"
 #include "follow_dlg.h"
-#include "follow.h"
+#include <epan/follow.h>
 #include "dlg_utils.h"
 #include "keys.h"
 #include "globals.h"
-#include "gtkglobals.h"
 #include "main.h"
+#include "alert_box.h"
 #include "simple_dialog.h"
-#include "prefs.h"
-#include "util.h"
-#include "ui_util.h"
+#include <epan/dissectors/packet-ipv6.h>
+#include <epan/prefs.h>
+#include <epan/addr_resolv.h>
+#include <epan/charsets.h>
+#include "tempfile.h"
+#include "gui_utils.h"
+#include <epan/epan_dissect.h>
+#include <epan/filesystem.h>
+#include "compat_macros.h"
+#include <epan/ipproto.h>
+#include "print_mswin.h"
+#include "font_utils.h"
+#include "help_dlg.h"
+
+/* This is backwards-compatibility code for old versions of GTK+ (2.2.1 and
+ * earlier).  It defines the new wrap behavior (unknown in earlier versions)
+ * as the old (slightly buggy) wrap behavior.
+ */
+#ifndef GTK_WRAP_WORD_CHAR
+#define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD
+#endif
 
+/* Show Stream */
+typedef enum {
+       FROM_CLIENT,
+       FROM_SERVER,
+       BOTH_HOSTS
+} show_stream_t;
+
+/* Show Type */
+typedef enum {
+       SHOW_ASCII,
+       SHOW_EBCDIC,
+       SHOW_HEXDUMP,
+       SHOW_CARRAY,
+       SHOW_RAW
+} show_type_t;
+
+typedef struct {
+       show_stream_t   show_stream;
+       show_type_t     show_type;
+       char            data_out_filename[128 + 1];
+       GtkWidget       *text;
+       GtkWidget       *ascii_bt;
+       GtkWidget       *ebcdic_bt;
+       GtkWidget       *hexdump_bt;
+       GtkWidget       *carray_bt;
+       GtkWidget       *raw_bt;
+       GtkWidget       *follow_save_as_w;
+       gboolean        is_ipv6;
+       char            *filter_out_filter;
+       GtkWidget       *filter_te;
+       GtkWidget       *streamwindow;
+} follow_info_t;
+
+static void follow_destroy_cb(GtkWidget * win, gpointer data);
+static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
+static void follow_load_text(follow_info_t *follow_info);
+static void follow_filter_out_stream(GtkWidget * w, gpointer parent_w);
+static void follow_print_stream(GtkWidget * w, gpointer parent_w);
+static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
+static void follow_save_as_ok_cb(GtkWidget * w, gpointer fs);
+static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
+static void follow_stream_om_both(GtkWidget * w, gpointer data);
+static void follow_stream_om_client(GtkWidget * w, gpointer data);
+static void follow_stream_om_server(GtkWidget * w, gpointer data);
+
+
+/* With MSVC and a libethereal.dll, we need a special declaration. */
+ETH_VAR_IMPORT FILE *data_out_file;
+
+#define E_FOLLOW_INFO_KEY "follow_info_key"
+
+/* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
+   so we can redraw them all if the colors or font changes. */
+static GList *follow_infos;
+
+/* Add a "follow_info_t" structure to the list. */
+static void
+remember_follow_info(follow_info_t *follow_info)
+{
+  follow_infos = g_list_append(follow_infos, follow_info);
+}
 
-static void follow_destroy_cb(GtkWidget *win, gpointer data);
-static void follow_charset_toggle_cb(GtkWidget *w, gpointer parent_w);
-static void follow_load_text(GtkWidget *text, char *filename, guint8 show_type);
-static void follow_print_stream(GtkWidget *w, gpointer parent_w);
-static void follow_save_as_cmd_cb(GtkWidget *w, gpointer data);
-static void follow_save_as_ok_cb(GtkWidget *w, GtkFileSelection *fs);
-static void follow_save_as_destroy_cb(GtkWidget *win, gpointer user_data);
+/* Remove a "follow_info_t" structure from the list. */
+static void
+forget_follow_info(follow_info_t *follow_info)
+{
+  follow_infos = g_list_remove(follow_infos, follow_info);
+}
 
-FILE        *data_out_file = NULL;
-static char data_out_filename[128+1];
+static void
+follow_redraw(gpointer data, gpointer user_data _U_)
+{
+       follow_load_text((follow_info_t *)data);
+}
+
+/* Redraw the text in all "Follow TCP Stream" windows. */
+void
+follow_redraw_all(void)
+{
+       g_list_foreach(follow_infos, follow_redraw, NULL);
+}
 
 /* Follow the TCP stream, if any, to which the last packet that we called
    a dissection routine on belongs (this might be the most recently
    selected packet, or it might be the last packet in the file). */
 void
-follow_stream_cb( GtkWidget *w, gpointer data )
+follow_stream_cb(GtkWidget * w, gpointer data _U_)
 {
-  GtkWidget *streamwindow, *box, *txt_scrollw, *text, *filter_te;
-  GtkWidget *hbox, *button;
-  GtkWidget *b_ascii, *b_ebcdic, *b_hexdump;
-  int        tmp_fd;
-  gchar     *follow_filter;
-
-  if( pi.ipproto == 6 ) {
-    /* we got tcp so we can follow */
-    /* Create a temporary file into which to dump the reassembled data
-       from the TCP stream, and set "data_out_file" to refer to it, so
-       that the TCP code will write to it.
-
-       XXX - it might be nicer to just have the TCP code directly
-       append stuff to the text widget for the TCP stream window,
-       if we can arrange that said window not pop up until we're
-       done. */
-    tmp_fd = create_tempfile( data_out_filename, sizeof data_out_filename, "follow");
-    if (tmp_fd == -1) {
-      simple_dialog(ESD_TYPE_WARN, NULL,
-        "Could not create temporary file %s: %s", data_out_filename, strerror(errno));
-      return;
-    }
-    data_out_file = fdopen( tmp_fd, "wb" );
-    if( data_out_file == NULL ) {
-      simple_dialog(ESD_TYPE_WARN, NULL,
-        "Could not create temporary file %s: %s", data_out_filename, strerror(errno));
-      close(tmp_fd);
-      unlink(data_out_filename);
-      return;
-    }
+       GtkWidget       *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
+       GtkWidget       *hbox, *bbox, *button, *radio_bt;
+    GtkWidget   *stream_fr, *stream_vb;
+       GtkWidget       *stream_om, *stream_menu, *stream_mi;
+       GtkTooltips *tooltips;
+       int                 tmp_fd;
+       gchar           *follow_filter;
+       const gchar     *previous_filter;
+    int                    filter_out_filter_len;
+       const char      *hostname0, *hostname1;
+       char            *port0, *port1;
+       char            string[128];
+       follow_tcp_stats_t stats;
+       follow_info_t   *follow_info;
+
+       /* we got tcp so we can follow */
+       if (cfile.edt->pi.ipproto != IP_PROTO_TCP) {
+               simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                             "Error following stream.  Please make\n"
+                             "sure you have a TCP packet selected.");
+               return;
+       }
 
-    /* Create a new filter that matches all packets in the TCP stream,
-       and set the display filter entry accordingly */
-    reset_tcp_reassembly();
-    follow_filter = build_follow_filter( &pi );
+       follow_info = g_new0(follow_info_t, 1);
+
+       /* Create a temporary file into which to dump the reassembled data
+          from the TCP stream, and set "data_out_file" to refer to it, so
+          that the TCP code will write to it.
+
+          XXX - it might be nicer to just have the TCP code directly
+          append stuff to the text widget for the TCP stream window,
+          if we can arrange that said window not pop up until we're
+          done. */
+       tmp_fd = create_tempfile(follow_info->data_out_filename,
+                       sizeof follow_info->data_out_filename, "follow");
+
+       if (tmp_fd == -1) {
+           simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                         "Could not create temporary file %s: %s",
+                         follow_info->data_out_filename, strerror(errno));
+           g_free(follow_info);
+           return;
+       }
 
-    /* set the display filter entry accordingly */
-    filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
-    gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
+       data_out_file = fdopen(tmp_fd, "wb");
+       if (data_out_file == NULL) {
+           simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                         "Could not create temporary file %s: %s",
+                         follow_info->data_out_filename, strerror(errno));
+           eth_close(tmp_fd);
+           unlink(follow_info->data_out_filename);
+           g_free(follow_info);
+           return;
+       }
 
-    /* Run the display filter so it goes in effect. */
-    filter_packets(&cfile, follow_filter);
+       /* Create a new filter that matches all packets in the TCP stream,
+          and set the display filter entry accordingly */
+       reset_tcp_reassembly();
+       follow_filter = build_follow_filter(&cfile.edt->pi);
+
+       /* Set the display filter entry accordingly */
+       filter_te = OBJECT_GET_DATA(w, E_DFILTER_TE_KEY);
+
+       /* needed in follow_filter_out_stream(), is there a better way? */
+       follow_info->filter_te = filter_te;
+
+       /* save previous filter, const since we're not supposed to alter */
+       previous_filter =
+           (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
+
+       /* allocate our new filter. API claims g_malloc terminates program on failure */
+       /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */
+       filter_out_filter_len = strlen(follow_filter) + strlen(previous_filter) + 16;
+       follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len);
+
+       /* append the negation */
+       if(strlen(previous_filter)) {
+           g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
+            "%s and !(%s)", previous_filter, follow_filter);
+       } else {
+           g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
+            "!(%s)", follow_filter);
+       }
+
+
+       gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
+
+       /* Run the display filter so it goes in effect - even if it's the
+          same as the previous display filter. */
+       main_filter_packets(&cfile, follow_filter, TRUE);
+
+       /* Free the filter string, as we're done with it. */
+       g_free(follow_filter);
+
+       /* The data_out_file should now be full of the streams information */
+       fclose(data_out_file);
+
+       /* The data_out_filename file now has all the text that was in the session */
+       streamwindow = dlg_window_new("Follow TCP Stream");
+
+       /* needed in follow_filter_out_stream(), is there a better way? */
+       follow_info->streamwindow = streamwindow;
 
-    /* the data_out_file should now be full of the streams information */
-    fclose( data_out_file );
+       gtk_widget_set_name(streamwindow, "TCP stream window");
+       gtk_window_set_default_size(GTK_WINDOW(streamwindow), DEF_WIDTH, DEF_HEIGHT);
+       gtk_container_border_width(GTK_CONTAINER(streamwindow), 6);
 
-    /* the data_out_filename file now has all the text that was in the session */
-    streamwindow = gtk_window_new( GTK_WINDOW_TOPLEVEL);
-    gtk_widget_set_name( streamwindow, "TCP stream window" );
+       /* setup the container */
+       tooltips = gtk_tooltips_new ();
 
-    gtk_signal_connect( GTK_OBJECT(streamwindow), "destroy",
-                       GTK_SIGNAL_FUNC(follow_destroy_cb), NULL);
-                       
-    if( incomplete_tcp_stream ) {
-      gtk_window_set_title( GTK_WINDOW(streamwindow), 
-                           "Contents of TCP stream (incomplete)" );
+       vbox = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
+
+       /* content frame */
+       if (incomplete_tcp_stream) {
+               stream_fr = gtk_frame_new("Stream Content (incomplete)");
+       } else {
+               stream_fr = gtk_frame_new("Stream Content");
+       }
+       gtk_container_add(GTK_CONTAINER(vbox), stream_fr);
+       gtk_widget_show(stream_fr);
+
+       stream_vb = gtk_vbox_new(FALSE, 6);
+       gtk_container_set_border_width( GTK_CONTAINER(stream_vb) , 6);
+       gtk_container_add(GTK_CONTAINER(stream_fr), stream_vb);
+
+       /* create a scrolled window for the text */
+       txt_scrollw = scrolled_window_new(NULL, NULL);
+#if GTK_MAJOR_VERSION >= 2
+       gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw),
+                                           GTK_SHADOW_IN);
+#endif
+       gtk_box_pack_start(GTK_BOX(stream_vb), txt_scrollw, TRUE, TRUE, 0);
+
+       /* create a text box */
+#if GTK_MAJOR_VERSION < 2
+       text = gtk_text_new(NULL, NULL);
+       gtk_text_set_editable(GTK_TEXT(text), FALSE);
+#else
+        text = gtk_text_view_new();
+       gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
+       gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
+#endif
+       gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
+       follow_info->text = text;
+
+
+       /* stream hbox */
+       hbox = gtk_hbox_new(FALSE, 1);
+       gtk_box_pack_start(GTK_BOX(stream_vb), hbox, FALSE, FALSE, 0);
+
+       /* Create Save As Button */
+       button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_SAVE_AS);
+       SIGNAL_CONNECT(button, "clicked", follow_save_as_cmd_cb, follow_info);
+       gtk_tooltips_set_tip (tooltips, button, "Save the content as currently displayed ", NULL);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+
+       /* Create Print Button */
+       button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_PRINT);
+       SIGNAL_CONNECT(button, "clicked", follow_print_stream, follow_info);
+       gtk_tooltips_set_tip (tooltips, button, "Print the content as currently displayed", NULL);
+       gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+
+       /* Stream to show */
+       follow_tcp_stats(&stats);
+
+       if (stats.is_ipv6) {
+         struct e_in6_addr ipaddr;
+         memcpy(&ipaddr, stats.ip_address[0], 16);
+         hostname0 = get_hostname6(&ipaddr);
+         memcpy(&ipaddr, stats.ip_address[0], 16);
+         hostname1 = get_hostname6(&ipaddr);
+       } else {
+         guint32 ipaddr;
+         memcpy(&ipaddr, stats.ip_address[0], 4);
+         hostname0 = get_hostname(ipaddr);
+         memcpy(&ipaddr, stats.ip_address[1], 4);
+         hostname1 = get_hostname(ipaddr);
+       }
+
+       port0 = get_tcp_port(stats.tcp_port[0]);
+       port1 = get_tcp_port(stats.tcp_port[1]);
+
+       follow_info->is_ipv6 = stats.is_ipv6;
+
+       stream_om = gtk_option_menu_new();
+       stream_menu = gtk_menu_new();
+
+       /* Both Stream Directions */
+       g_snprintf(string, sizeof(string),
+                "Entire conversation (%u bytes)",
+                stats.bytes_written[0] + stats.bytes_written[1]);
+       stream_mi = gtk_menu_item_new_with_label(string);
+       SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_both,
+                       follow_info);
+       gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
+       gtk_widget_show(stream_mi);
+       follow_info->show_stream = BOTH_HOSTS;
+
+       /* Host 0 --> Host 1 */
+       g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
+                hostname0, port0, hostname1, port1,
+                stats.bytes_written[0]);
+       stream_mi = gtk_menu_item_new_with_label(string);
+       SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_client,
+                       follow_info);
+       gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
+       gtk_widget_show(stream_mi);
+
+       /* Host 1 --> Host 0 */
+       g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
+                hostname1, port1, hostname0, port0,
+                stats.bytes_written[1]);
+       stream_mi = gtk_menu_item_new_with_label(string);
+       SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_server,
+                       follow_info);
+       gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
+       gtk_widget_show(stream_mi);
+
+       gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
+       /* Set history to 0th item, i.e., the first item. */
+       gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
+       gtk_tooltips_set_tip (tooltips, stream_om,
+           "Select the stream direction to display", NULL);
+       gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
+
+       /* ASCII radio button */
+       radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
+       gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
+       gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
+       SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
+                       follow_info);
+       follow_info->ascii_bt = radio_bt;
+       follow_info->show_type = SHOW_ASCII;
+
+       /* EBCDIC radio button */
+       radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
+                                           (GTK_RADIO_BUTTON(radio_bt)),
+                                           "EBCDIC");
+       gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"EBCDIC\" format", NULL);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
+       gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
+       SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
+                       follow_info);
+       follow_info->ebcdic_bt = radio_bt;
+
+       /* HEX DUMP radio button */
+       radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
+                                           (GTK_RADIO_BUTTON(radio_bt)),
+                                           "Hex Dump");
+       gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
+       gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
+       SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
+                       follow_info);
+       follow_info->hexdump_bt = radio_bt;
+
+       /* C Array radio button */
+       radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
+                                           (GTK_RADIO_BUTTON(radio_bt)),
+                                           "C Arrays");
+       gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
+       gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
+       SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
+                       follow_info);
+       follow_info->carray_bt = radio_bt;
+
+       /* Raw radio button */
+       radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
+                                           (GTK_RADIO_BUTTON(radio_bt)),
+                                           "Raw");
+       gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. "
+        "As this contains non printable characters, the screen output will be in ASCII format", NULL);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
+       gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
+       SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
+                       follow_info);
+       follow_info->raw_bt = radio_bt;
+
+    /* Button row: (help), filter out, close button */
+    if(topic_available(HELP_FILESET_DIALOG)) {
+      bbox = dlg_button_row_new(ETHEREAL_STOCK_FILTER_OUT_STREAM, GTK_STOCK_CLOSE, GTK_STOCK_HELP, NULL);
     } else {
-      gtk_window_set_title( GTK_WINDOW(streamwindow),
-                           "Contents of TCP stream" );
+      bbox = dlg_button_row_new(ETHEREAL_STOCK_FILTER_OUT_STREAM, GTK_STOCK_CLOSE, NULL);
     }
-    gtk_widget_set_usize( GTK_WIDGET(streamwindow), DEF_WIDTH, DEF_HEIGHT );
-    gtk_container_border_width( GTK_CONTAINER(streamwindow), 2 );
-
-    /* setup the container */
-    box = gtk_vbox_new( FALSE, 0 );
-    gtk_container_add( GTK_CONTAINER(streamwindow), box );
-    gtk_widget_show( box );
-
-    /* create a scrolled window for the text */
-    txt_scrollw = gtk_scrolled_window_new( NULL, NULL );
-    gtk_box_pack_start( GTK_BOX(box), txt_scrollw, TRUE, TRUE, 0 );
-    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(txt_scrollw),
-                                       GTK_POLICY_NEVER,
-                                       GTK_POLICY_ALWAYS );
-    set_scrollbar_placement_scrollw(txt_scrollw, prefs.gui_scrollbar_on_right);
-    remember_scrolled_window(txt_scrollw);
-    gtk_widget_show( txt_scrollw );
-
-    /* create a text box */
-    text = gtk_text_new( NULL, NULL );
-    gtk_text_set_editable( GTK_TEXT(text), FALSE);
-    gtk_container_add( GTK_CONTAINER(txt_scrollw), text );
-    gtk_widget_show(text);
-
-    /* Create hbox */
-    hbox = gtk_hbox_new( FALSE, 1 );
-    gtk_box_pack_end( GTK_BOX(box), hbox, FALSE, FALSE, 0);
-    gtk_widget_show(hbox);
-
-#define E_FOLLOW_ASCII_KEY "follow_ascii_key"
-#define E_FOLLOW_EBCDIC_KEY "follow_ebcdic_key"
-#define E_FOLLOW_HEXDUMP_KEY "follow_hexdump_key"
-
-    /* Create Radio Buttons */
-    b_ascii = gtk_radio_button_new_with_label(NULL, "ASCII");
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_ascii), TRUE);
-    gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_ASCII_KEY, b_ascii);
-    gtk_box_pack_start(GTK_BOX(hbox), b_ascii, FALSE, FALSE, 0);
-    gtk_signal_connect(GTK_OBJECT(b_ascii), "toggled",
-                   GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
-                   GTK_OBJECT(streamwindow));
-    gtk_widget_show(b_ascii);
-
-    b_ebcdic = gtk_radio_button_new_with_label(
-                   gtk_radio_button_group(GTK_RADIO_BUTTON(b_ascii)),
-                   "EBCDIC");
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_ebcdic), FALSE);
-    gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_EBCDIC_KEY, b_ebcdic);
-    gtk_box_pack_start(GTK_BOX(hbox), b_ebcdic, FALSE, FALSE, 0);
-    gtk_signal_connect(GTK_OBJECT(b_ebcdic), "toggled",
-                   GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
-                   GTK_OBJECT(streamwindow));
-    gtk_widget_show(b_ebcdic);
-
-    b_hexdump = gtk_radio_button_new_with_label(
-                   gtk_radio_button_group(GTK_RADIO_BUTTON(b_ascii)),
-                   "Hex. Dump");
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b_hexdump), FALSE);
-    gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_HEXDUMP_KEY, b_hexdump);
-    gtk_box_pack_start(GTK_BOX(hbox), b_hexdump, FALSE, FALSE, 0);
-    gtk_signal_connect(GTK_OBJECT(b_hexdump), "toggled",
-                   GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
-                   GTK_OBJECT(streamwindow));
-    gtk_widget_show(b_hexdump);
-
-    /* Create Close Button */
-    button = gtk_button_new_with_label("Close");
-    gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
-                   GTK_SIGNAL_FUNC(gtk_widget_destroy),
-                   GTK_OBJECT(streamwindow));
-    gtk_box_pack_end( GTK_BOX(hbox), button, FALSE, FALSE, 0);
-    gtk_widget_show( button );
-
-    /* Create Save As Button */
-    button = gtk_button_new_with_label("Save As");
-    gtk_signal_connect(GTK_OBJECT(button), "clicked",
-                   GTK_SIGNAL_FUNC(follow_save_as_cmd_cb),
-                   GTK_OBJECT(streamwindow));
-    gtk_box_pack_end( GTK_BOX(hbox), button, FALSE, FALSE, 0);
-    gtk_widget_show( button );
-
-    /* Create Print Button */
-    button = gtk_button_new_with_label("Print");
-    gtk_signal_connect(GTK_OBJECT(button), "clicked",
-                   GTK_SIGNAL_FUNC(follow_print_stream),
-                   GTK_OBJECT(streamwindow));
-    gtk_box_pack_end( GTK_BOX(hbox), button, FALSE, FALSE, 0);
-    gtk_widget_show( button );
-
-    /* Tuck away the textbox into streamwindow */
-#define E_FOLLOW_TEXT_KEY "follow_text_key"
-    gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_TEXT_KEY, text);
-
-    follow_load_text(text, data_out_filename, 0);
+    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 5);
 
-    data_out_file = NULL;
 
-    /* Make sure this widget gets destroyed if we quit the main loop,
-       so that if we exit, we clean up any temporary files we have
-       for "Follow TCP Stream" windows. */
-    gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
-    gtk_widget_show( streamwindow );
-  } else {
-    simple_dialog(ESD_TYPE_CRIT, NULL,
-      "Error following stream.  Please make\n"
-      "sure you have a TCP packet selected.");
-  }
+    button = OBJECT_GET_DATA(bbox, ETHEREAL_STOCK_FILTER_OUT_STREAM);
+       gtk_tooltips_set_tip (tooltips, button,
+        "Build a display filter which cuts this stream from the capture", NULL);
+       SIGNAL_CONNECT(button, "clicked", follow_filter_out_stream, follow_info);
+
+    button = OBJECT_GET_DATA(bbox, GTK_STOCK_CLOSE);
+       window_set_cancel_button(streamwindow, button, window_cancel_button_cb);
+       gtk_tooltips_set_tip (tooltips, button,
+           "Close the dialog and keep the current display filter", NULL);
+    gtk_widget_grab_default(button);
+
+    if(topic_available(HELP_FILESET_DIALOG)) {
+      button = OBJECT_GET_DATA(bbox, GTK_STOCK_HELP);
+      SIGNAL_CONNECT(button, "clicked", topic_cb, HELP_FOLLOW_TCP_STREAM_DIALOG);
+    }
+
+       /* Tuck away the follow_info object into the window */
+       OBJECT_SET_DATA(streamwindow, E_FOLLOW_INFO_KEY, follow_info);
+
+       follow_load_text(follow_info);
+       remember_follow_info(follow_info);
+
+       data_out_file = NULL;
+
+       SIGNAL_CONNECT(streamwindow, "delete_event", window_delete_event_cb, NULL);
+       SIGNAL_CONNECT(streamwindow, "destroy", follow_destroy_cb, NULL);
+
+       /* Make sure this widget gets destroyed if we quit the main loop,
+          so that if we exit, we clean up any temporary files we have
+          for "Follow TCP Stream" windows. */
+       gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
+
+       gtk_widget_show_all(streamwindow);
+       window_present(streamwindow);
 }
 
 /* The destroy call back has the responsibility of
- * unlinking the temporary file */
+ * unlinking the temporary file
+ * and freeing the filter_out_filter */
 static void
-follow_destroy_cb(GtkWidget *win, gpointer data)
+follow_destroy_cb(GtkWidget *w, gpointer data _U_)
 {
-       unlink(data_out_filename);
-       gtk_widget_destroy(win);
+       follow_info_t   *follow_info;
+       int i;
+
+       follow_info = OBJECT_GET_DATA(w, E_FOLLOW_INFO_KEY);
+       i = unlink(follow_info->data_out_filename);
+       if(i != 0) {
+               g_warning("Follow: Couldn't remove temporary file: \"%s\", errno: %s (%u)", 
+                   follow_info->data_out_filename, strerror(errno), errno);        
+       }
+       g_free(follow_info->filter_out_filter);
+       forget_follow_info(follow_info);
+       g_free(follow_info);
 }
 
-#define E_FOLLOW_ASCII_TYPE    0
-#define E_FOLLOW_EBCDIC_TYPE   1
-#define E_FOLLOW_HEXDUMP_TYPE  2
+/* XXX - can I emulate follow_charset_toggle_cb() instead of having
+ * 3 different functions here?
+ * That might not be a bad idea, as it might mean we only reload
+ * the window once, not twice - see follow_charset_toggle_cb()
+ * for an explanation. */
+static void
+follow_stream_om_both(GtkWidget *w _U_, gpointer data)
+{
+       follow_info_t   *follow_info = data;
+       follow_info->show_stream = BOTH_HOSTS;
+       follow_load_text(follow_info);
+}
 
-/* Handles the ASCII/EBCDIC toggling */
 static void
-follow_charset_toggle_cb(GtkWidget *w, gpointer parent_w)
+follow_stream_om_client(GtkWidget *w _U_, gpointer data)
 {
-       guint8          show_type = E_FOLLOW_ASCII_TYPE;
-       GtkWidget       *b_ascii, *b_ebcdic, *b_hexdump, *text;
-
-       b_ascii = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                                                  E_FOLLOW_ASCII_KEY);
-       b_ebcdic = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                                                   E_FOLLOW_EBCDIC_KEY);
-       b_hexdump = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                                                    E_FOLLOW_HEXDUMP_KEY);
-       text = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                                               E_FOLLOW_TEXT_KEY);
-
-       g_assert(b_ascii);
-       g_assert(b_ebcdic);
-       g_assert(b_hexdump);
-       g_assert(text);
-
-       if (GTK_TOGGLE_BUTTON(b_ebcdic)->active)
-               show_type = E_FOLLOW_EBCDIC_TYPE;
-       else if (GTK_TOGGLE_BUTTON(b_hexdump)->active)
-               show_type = E_FOLLOW_HEXDUMP_TYPE;
-
-       follow_load_text(text, data_out_filename, show_type);
+       follow_info_t   *follow_info = data;
+       follow_info->show_stream = FROM_CLIENT;
+       follow_load_text(follow_info);
 }
 
-#define FLT_BUF_SIZE 1024
 static void
-follow_read_stream(char *filename, guint8 show_type,
-   void (*print_line)(char *, int, gboolean, void *), void *arg)
+follow_stream_om_server(GtkWidget *w _U_, gpointer data)
+{
+       follow_info_t   *follow_info = data;
+       follow_info->show_stream = FROM_SERVER;
+       follow_load_text(follow_info);
+}
+
+
+/* Handles the display style toggling */
+static void
+follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
+{
+       follow_info_t   *follow_info = data;
+
+       /*
+        * A radio button toggles when it goes on and when it goes
+        * off, so when you click a radio button two signals are
+        * delivered.  We only want to reprocess the display once,
+        * so we do it only when the button goes on.
+        */
+       if (GTK_TOGGLE_BUTTON(w)->active) {
+               if (w == follow_info->ebcdic_bt)
+                       follow_info->show_type = SHOW_EBCDIC;
+               else if (w == follow_info->hexdump_bt)
+                       follow_info->show_type = SHOW_HEXDUMP;
+               else if (w == follow_info->carray_bt)
+                       follow_info->show_type = SHOW_CARRAY;
+               else if (w == follow_info->ascii_bt)
+                       follow_info->show_type = SHOW_ASCII;
+               else if (w == follow_info->raw_bt)
+                       follow_info->show_type = SHOW_RAW;
+               follow_load_text(follow_info);
+       }
+}
+
+#define FLT_BUF_SIZE 1024
+
+typedef enum {
+       FRS_OK,
+       FRS_OPEN_ERROR,
+       FRS_READ_ERROR,
+       FRS_PRINT_ERROR
+} frs_return_t;
+
+/*
+ * XXX - the routine pointed to by "print_line" doesn't get handed lines,
+ * it gets handed bufferfuls.  That's fine for "follow_write_raw()"
+ * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
+ * the "print_line()" routine from "print.c", and as that routine might
+ * genuinely expect to be handed a line (if, for example, it's using
+ * some OS or desktop environment's printing API, and that API expects
+ * to be handed lines), "follow_print_text()" should probably accumulate
+ * lines in a buffer and hand them "print_line()".  (If there's a
+ * complete line in a buffer - i.e., there's nothing of the line in
+ * the previous buffer or the next buffer - it can just hand that to
+ * "print_line()" after filtering out non-printables, as an
+ * optimization.)
+ *
+ * This might or might not be the reason why C arrays display
+ * correctly but get extra blank lines very other line when printed.
+ */
+static frs_return_t
+follow_read_stream(follow_info_t *follow_info,
+                  gboolean (*print_line) (char *, size_t, gboolean, void *),
+                  void *arg)
 {
-  tcp_stream_chunk sc;
-  int bcount;
-  guint32 client_addr = 0;
-  guint16 client_port = 0;
-  gboolean is_server;
-  guint16 current_pos, global_client_pos = 0, global_server_pos = 0;
-  guint16 *global_pos;
-
-  data_out_file = fopen( filename, "rb" );
-  if( data_out_file ) {
-    char buffer[FLT_BUF_SIZE];
-    int nchars;
-    while(fread(&sc.src_addr, 1, sizeof(sc), data_out_file)) {
-      if (client_addr == 0) {
-        client_addr = sc.src_addr;
-        client_port = sc.src_port;
-      }
-      if (client_addr == sc.src_addr && client_port == sc.src_port) {
-       is_server = FALSE;
-       global_pos = &global_client_pos;
-      }
-      else {
-       is_server = TRUE;
-       global_pos = &global_server_pos;
-      }
-        
-      while (sc.dlen > 0) {
-        bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
-        nchars = fread( buffer, 1, bcount, data_out_file );
-        if (nchars == 0)
-          break;
-        sc.dlen -= bcount;
-       switch (show_type) {
-       case E_FOLLOW_EBCDIC_TYPE:
-               /* If our native arch is ASCII, call: */
-               EBCDIC_to_ASCII(buffer, nchars);
-       case E_FOLLOW_ASCII_TYPE:
-               /* If our native arch is EBCDIC, call:
-                * ASCII_TO_EBCDIC(buffer, nchars);
-                */
-               (*print_line)( buffer, nchars, is_server, arg );
+    tcp_stream_chunk   sc;
+    int                        bcount, iplen;
+    guint8             client_addr[MAX_IPADDR_LEN];
+    guint16            client_port = 0;
+    gboolean           is_server;
+    guint32            current_pos, global_client_pos = 0, global_server_pos = 0;
+    guint32            *global_pos;
+    gboolean           skip;
+    gchar               initbuf[256];
+    guint32             server_packet_count = 0;
+    guint32             client_packet_count = 0;
+    char                buffer[FLT_BUF_SIZE];
+    size_t              nchars;
+    static const gchar hexchars[16] = "0123456789abcdef";
+
+    iplen = (follow_info->is_ipv6) ? 16 : 4;
+
+    data_out_file = eth_fopen(follow_info->data_out_filename, "rb");
+    if (data_out_file == NULL) {
+       simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                     "Could not open temporary file %s: %s", follow_info->data_out_filename,
+                     strerror(errno));
+       return FRS_OPEN_ERROR;
+    }
+
+    while (fread(&sc, 1, sizeof(sc), data_out_file)) {
+       if (client_port == 0) {
+           memcpy(client_addr, sc.src_addr, iplen);
+           client_port = sc.src_port;
+       }
+       skip = FALSE;
+       if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
+           client_port == sc.src_port) {
+           is_server = FALSE;
+           global_pos = &global_client_pos;
+           if (follow_info->show_stream == FROM_SERVER) {
+               skip = TRUE;
+           }
+       }
+       else {
+           is_server = TRUE;
+           global_pos = &global_server_pos;
+           if (follow_info->show_stream == FROM_CLIENT) {
+               skip = TRUE;
+           }
+       }
+
+       while (sc.dlen > 0) {
+           bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
+           nchars = fread(buffer, 1, bcount, data_out_file);
+           if (nchars == 0)
                break;
-       case E_FOLLOW_HEXDUMP_TYPE:
-               current_pos = 0;
-               while (current_pos < nchars)
-               {
-                   gchar hexbuf[256];
-                   gchar hexchars[] = "0123456789abcdef";
-                   int i, cur;
-                   /* is_server indentation : put 63 spaces at the begenning
-                    * of the string */
-                   sprintf(hexbuf, is_server ?
-                           "                                 "
-                           "                              %08X  " :
-                           "%08X  ", *global_pos);
-                   cur = strlen(hexbuf);
-                   for (i=0; i < 16 && current_pos+i < nchars; i++) {
-                       hexbuf[cur++] = hexchars[(buffer[current_pos+i] & 0xf0) >> 4];
-                       hexbuf[cur++] = hexchars[buffer[current_pos+i] & 0x0f];
-                       if (i == 7) {
-                           hexbuf[cur++] = ' '; hexbuf[cur++] = ' ';
+           sc.dlen -= nchars;
+
+           if (!skip) {
+               switch (follow_info->show_type) {
+
+               case SHOW_EBCDIC:
+                   /* If our native arch is ASCII, call: */
+                   EBCDIC_to_ASCII(buffer, nchars);
+                   if (!(*print_line) (buffer, nchars, is_server, arg))
+                       goto print_error;
+                   break;
+
+               case SHOW_ASCII:
+                   /* If our native arch is EBCDIC, call:
+                    * ASCII_TO_EBCDIC(buffer, nchars);
+                    */
+                   if (!(*print_line) (buffer, nchars, is_server, arg))
+                       goto print_error;
+                   break;
+
+               case SHOW_RAW:
+                   /* Don't translate, no matter what the native arch
+                    * is.
+                    */
+                   if (!(*print_line) (buffer, nchars, is_server, arg))
+                       goto print_error;
+                   break;
+
+               case SHOW_HEXDUMP:
+                   current_pos = 0;
+                   while (current_pos < nchars) {
+                       gchar hexbuf[256];
+                       int i;
+                       gchar *cur = hexbuf, *ascii_start;
+
+                       /* is_server indentation : put 78 spaces at the
+                        * beginning of the string */
+                       if (is_server && follow_info->show_stream == BOTH_HOSTS) {
+                           memset(cur, ' ', 78);
+                           cur += 78;
+                       }
+                       cur += g_snprintf(cur, 20, "%08X  ", *global_pos);
+                       /* 49 is space consumed by hex chars */
+                       ascii_start = cur + 49;
+                       for (i = 0; i < 16 && current_pos + i < nchars; i++) {
+                           *cur++ =
+                               hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
+                           *cur++ =
+                               hexchars[buffer[current_pos + i] & 0x0f];
+                           *cur++ = ' ';
+                           if (i == 7)
+                               *cur++ = ' ';
+                       }
+                       /* Fill it up if column isn't complete */
+                       while (cur < ascii_start)  
+                           *cur++ = ' ';
+
+                       /* Now dump bytes as text */
+                       for (i = 0; i < 16 && current_pos + i < nchars; i++) {
+                           *cur++ =
+                               (isprint((guchar)buffer[current_pos + i]) ?
+                               buffer[current_pos + i] : '.' );
+                           if (i == 7) {
+                               *cur++ = ' ';
+                           }
                        }
-                       else if (i != 15)
+                       current_pos += i;
+                       (*global_pos) += i;
+                       *cur++ = '\n';
+                       *cur = 0;
+                       if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
+                           goto print_error;
+                   }
+                   break;
+
+               case SHOW_CARRAY:
+                   current_pos = 0;
+                   g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n", 
+                           is_server ? 1 : 0, 
+                           is_server ? server_packet_count++ : client_packet_count++);
+                   if (!(*print_line) (initbuf, strlen(initbuf), is_server, arg))
+                       goto print_error;
+                   while (current_pos < nchars) {
+                       gchar hexbuf[256];
+                       int i, cur;
+
+                       cur = 0;
+                       for (i = 0; i < 8 && current_pos + i < nchars; i++) {
+                         /* Prepend entries with "0x" */
+                         hexbuf[cur++] = '0';
+                         hexbuf[cur++] = 'x';
+                           hexbuf[cur++] =
+                               hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
+                           hexbuf[cur++] =
+                               hexchars[buffer[current_pos + i] & 0x0f];
+
+                           /* Delimit array entries with a comma */
+                           if (current_pos + i + 1 < nchars)
+                             hexbuf[cur++] = ',';
+
                            hexbuf[cur++] = ' ';
+                       }
+
+                       /* Terminate the array if we are at the end */
+                       if (current_pos + i == nchars) {
+                           hexbuf[cur++] = '}';
+                           hexbuf[cur++] = ';';
+                       }
+
+                       current_pos += i;
+                       (*global_pos) += i;
+                       hexbuf[cur++] = '\n';
+                       hexbuf[cur] = 0;
+                       if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
+                           goto print_error;
                    }
-                   current_pos += i;
-                   (*global_pos) += i;
-                   hexbuf[cur++] = '\n';
-                   hexbuf[cur] = 0;
-                   (*print_line)( hexbuf, strlen(hexbuf), is_server, arg );
+                   break;
                }
-               break;
+           }
        }
-      }
     }
-    if( ferror( data_out_file ) ) {
-      simple_dialog(ESD_TYPE_WARN, NULL,
-        "Error reading temporary file %s: %s", filename, strerror(errno));
+    if (ferror(data_out_file)) {
+       simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                     "Error reading temporary file %s: %s", follow_info->data_out_filename,
+                     strerror(errno));
+       fclose(data_out_file);
+       data_out_file = NULL;
+       return FRS_READ_ERROR;
     }
-    fclose( data_out_file );
+
+       fclose(data_out_file);
+       data_out_file = NULL;
+    return FRS_OK;
+
+print_error:
+    fclose(data_out_file);
     data_out_file = NULL;
-  } else {
-    simple_dialog(ESD_TYPE_WARN, NULL,
-      "Could not open temporary file %s: %s", filename, strerror(errno));
-  }
+    return FRS_PRINT_ERROR;
 }
 
 /*
  * XXX - for text printing, we probably want to wrap lines at 80 characters;
- * for PostScript printing, we probably want to wrap them at the appropriate
- * width, and perhaps put some kind of dingbat (to use the technical term)
- * to indicate a wrapped line, along the lines of what's done when displaying
- * this in a window, as per Warren Young's suggestion.
- *
- * For now, we support only text printing.
+ * (PostScript printing is doing this already), and perhaps put some kind of 
+ * dingbat (to use the technical term) to indicate a wrapped line, along the 
+ * lines of what's done when displaying this in a window, as per Warren Young's 
+ * suggestion.
  */
-static void
-follow_print_text(char *buffer, int nchars, gboolean is_server, void *arg)
+static gboolean
+follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
 {
-  FILE *fh = arg;
+    print_stream_t *stream = arg;
+    size_t i;
+    char *str;
+
+    /* convert non printable characters */
+    for (i = 0; i < nchars; i++) {
+        if (buffer[i] == '\n' || buffer[i] == '\r')
+            continue;
+        if (! isprint((guchar)buffer[i])) {
+            buffer[i] = '.';
+        }
+    }
+
+    /* convert unterminated char array to a zero terminated string */
+    str = g_malloc(nchars + 1);
+    memcpy(str, buffer, nchars);
+    str[nchars] = 0;
+    print_line(stream, /*indent*/ 0, str);
+    g_free(str);
 
-  fwrite(buffer, nchars, 1, fh);
+    return TRUE;
+}
+
+static gboolean
+follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
+{
+    FILE *fh = arg;
+    size_t nwritten;
+
+    nwritten = fwrite(buffer, 1, nchars, fh);
+    if (nwritten != nchars)
+       return FALSE;
+
+    return TRUE;
 }
 
 static void
-follow_print_stream(GtkWidget *w, gpointer parent_w)
+follow_filter_out_stream(GtkWidget * w _U_, gpointer data)
 {
-       FILE *fh;
-       gboolean to_file;
-       char* print_dest;
-       guint8 show_type = E_FOLLOW_ASCII_TYPE;
-       GtkWidget *button;
-
-       switch (prefs.pr_dest) {
-               case PR_DEST_CMD:
-                       print_dest = prefs.pr_cmd;
-                       to_file = FALSE;
-                       break;
-
-               case PR_DEST_FILE:
-                       print_dest = prefs.pr_file;
-                       to_file = TRUE;
-                       break;
-               default: /* "Can't happen" */
-                       simple_dialog(ESD_TYPE_CRIT, NULL,
-                               "Couldn't figure out where to send the print "
-                               "job. Check your preferences.");
-                       return;
-       }
-
-       fh = open_print_dest(to_file, print_dest);
-       if (fh == NULL) {
-               switch (to_file) {
-                       case FALSE:
-                               simple_dialog(ESD_TYPE_WARN, NULL,
-                                               "Couldn't run print command %s.", prefs.pr_cmd);
-                               break;
-
-                       case TRUE:
-                               simple_dialog(ESD_TYPE_WARN, NULL, 
-                                               file_write_error_message(errno),
-                                               prefs.pr_file);
-                               break;
-               }
-               return;
-       }
-
-       button = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                       E_FOLLOW_EBCDIC_KEY);
-       if (GTK_TOGGLE_BUTTON(button)->active)
-               show_type = E_FOLLOW_EBCDIC_TYPE;
-       button = (GtkWidget*) gtk_object_get_data(GTK_OBJECT(parent_w),
-                       E_FOLLOW_HEXDUMP_KEY);
-       if (GTK_TOGGLE_BUTTON(button)->active)
-               show_type = E_FOLLOW_HEXDUMP_TYPE;
-
-       print_preamble(fh, PR_FMT_TEXT);
-       follow_read_stream(data_out_filename, show_type, follow_print_text, fh);
-       print_finale(fh, PR_FMT_TEXT);
-       close_print_dest(to_file, fh);
+    follow_info_t      *follow_info = data;
+
+    /* Lock out user from messing with us. (ie. don't free our data!) */
+    gtk_widget_set_sensitive(follow_info->streamwindow, FALSE);
+
+    /* Set the display filter. */
+    gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te), follow_info->filter_out_filter);
+
+    /* Run the display filter so it goes in effect. */
+    main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE);
+
+    /* we force a subsequent close */
+    window_destroy(follow_info->streamwindow);
+
+    return;
 }
 
 static void
-follow_add_to_gtk_text(char *buffer, int nchars, gboolean is_server, void *arg)
+follow_print_stream(GtkWidget * w _U_, gpointer data)
+{
+    print_stream_t     *stream;
+    gboolean           to_file;
+    char               *print_dest;
+    follow_info_t      *follow_info = data;
+#ifdef _WIN32
+    gboolean win_printer = FALSE;
+#endif
+
+    switch (prefs.pr_dest) {
+    case PR_DEST_CMD:
+#ifdef _WIN32
+        win_printer = TRUE;
+        /*XXX should use temp file stuff in util routines */
+        print_dest = g_strdup(tmpnam(NULL));
+        to_file = TRUE;
+#else
+        print_dest = prefs.pr_cmd;
+        to_file = FALSE;
+#endif
+        break;
+    case PR_DEST_FILE:
+        print_dest = prefs.pr_file;
+        to_file = TRUE;
+        break;
+    default:                   /* "Can't happen" */
+        simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+            "Couldn't figure out where to send the print "
+            "job. Check your preferences.");
+        return;
+    }
+
+    switch (prefs.pr_format) {
+
+    case PR_FMT_TEXT:
+      stream = print_stream_text_new(to_file, print_dest);
+      break;
+
+    case PR_FMT_PS:
+      stream = print_stream_ps_new(to_file, print_dest);
+      break;
+
+    default:
+      g_assert_not_reached();
+      stream = NULL;
+    }
+    if (stream == NULL) {
+        if (to_file) {
+            open_failure_alert_box(prefs.pr_file, errno, TRUE);
+        } else {
+            simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                     "Couldn't run print command %s.", prefs.pr_cmd);
+        }
+        return;
+    }
+
+    if (!print_preamble(stream, cfile.filename))
+        goto print_error;
+
+    switch (follow_read_stream(follow_info, follow_print_text, stream)) {
+    case FRS_OK:
+       break;
+    case FRS_OPEN_ERROR:
+    case FRS_READ_ERROR:
+           /* XXX - cancel printing? */
+           destroy_print_stream(stream);
+           return;
+    case FRS_PRINT_ERROR:
+           goto print_error;
+    }
+
+    if (!print_finale(stream))
+           goto print_error;
+
+    if (!destroy_print_stream(stream)) {
+        if (to_file) {
+          write_failure_alert_box(prefs.pr_file, errno);
+        } else {
+            simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+                         "Error closing print destination.");
+        }
+    }
+#ifdef _WIN32
+    if (win_printer) {
+        print_mswin(print_dest);
+
+        /* trash temp file */
+        eth_remove(print_dest);
+    }
+#endif
+    return;
+
+print_error:
+    if (to_file) {
+        write_failure_alert_box(prefs.pr_file, errno);
+    } else {
+        simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+          "Error writing to print command: %s", strerror(errno));
+    }
+    /* XXX - cancel printing? */
+    destroy_print_stream(stream);
+
+#ifdef _WIN32
+    if (win_printer) {
+        /* trash temp file */
+        eth_remove(print_dest);
+    }
+#endif
+}
+
+/* static variable declarations to speed up the performance
+ * of follow_load_text and follow_add_to_gtk_text
+ */
+static GdkColor server_fg, server_bg;
+static GdkColor client_fg, client_bg;
+#if GTK_MAJOR_VERSION >= 2
+static GtkTextTag *server_tag, *client_tag;
+#endif
+
+static gboolean
+follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
+                      void *arg)
 {
-  GtkWidget *text = arg;
-
-  if (is_server)
-    gtk_text_insert( GTK_TEXT(text), m_r_font, &prefs.st_server_fg,
-            &prefs.st_server_bg, buffer, nchars );
-  else
-    gtk_text_insert( GTK_TEXT(text), m_r_font, &prefs.st_client_fg,
-            &prefs.st_client_bg, buffer, nchars );
+    GtkWidget *text = arg;
+#if GTK_MAJOR_VERSION >= 2
+    GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
+    GtkTextIter    iter;
+#endif
+
+#if GTK_MAJOR_VERSION >= 2 || GTK_MINOR_VERSION >= 3
+    gboolean line_break = FALSE;
+    /* While our isprint() hack is in place, we
+     * have to use convert some chars to '.' in order
+     * to be able to see the data we *should* see
+     * in the GtkText widget.
+     */
+    size_t i;
+
+    for (i = 0; i < nchars; i++) {
+        if (buffer[i] == '\n' || buffer[i] == '\r')
+        {
+            line_break = TRUE;
+            continue;
+        }
+        if (! isprint(buffer[i])) {
+            buffer[i] = '.';
+        }
+    }
+
+    /* XXX - workaround for bug 852
+     * Force a line break so that the text view 
+     * doesn't blow up on excessive long lines.
+     */
+    if (line_break == FALSE)
+        buffer[--i] = '\n';
+#endif
+
+#if GTK_MAJOR_VERSION < 2
+    if (is_server) {
+       gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg, 
+                       &server_bg, buffer, nchars);
+    } else {
+       gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg, 
+                       &client_bg, buffer, nchars);
+    }
+#else
+    gtk_text_buffer_get_end_iter(buf, &iter);
+    if (is_server) {
+       gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
+                                       server_tag, NULL);
+    } else {
+       gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
+                                       client_tag, NULL);
+    }
+#endif
+    return TRUE;
 }
 
 static void
-follow_load_text(GtkWidget *text, char *filename, guint8 show_type)
+follow_load_text(follow_info_t *follow_info)
 {
-  int bytes_already;
-
-  /* Delete any info already in text box */
-  bytes_already = gtk_text_get_length(GTK_TEXT(text));
-  if (bytes_already > 0) {
-    gtk_text_set_point(GTK_TEXT(text), 0);
-    gtk_text_forward_delete(GTK_TEXT(text), bytes_already);
-  }
-
-  /* stop the updates while we fill the text box */
-  gtk_text_freeze( GTK_TEXT(text) );
-  follow_read_stream(filename, show_type, follow_add_to_gtk_text, text);
-  gtk_text_thaw( GTK_TEXT(text) );
+#if GTK_MAJOR_VERSION < 2
+    int bytes_already;
+#else
+    GtkTextBuffer *buf;
+
+    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
+#endif
+
+    /* prepare colors one time for repeated use by follow_add_to_gtk_text */
+    color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
+    color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
+    color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
+    color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
+
+    /* Delete any info already in text box */
+#if GTK_MAJOR_VERSION < 2
+    bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
+    if (bytes_already > 0) {
+       gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
+       gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
+    }
+
+    /* stop the updates while we fill the text box */
+    gtk_text_freeze(GTK_TEXT(follow_info->text));
+#else
+    /* prepare tags one time for repeated use by follow_add_to_gtk_text */
+    server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg,
+                                     "background-gdk", &server_bg, "font-desc",
+                                     user_font_get_regular(), NULL);
+    client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg,
+                                     "background-gdk", &client_bg, "font-desc",
+                                     user_font_get_regular(), NULL);
+
+    gtk_text_buffer_set_text(buf, "", -1);
+#endif
+    follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
+#if GTK_MAJOR_VERSION < 2
+    gtk_text_thaw(GTK_TEXT(follow_info->text));
+#endif
 }
 
-       
+
 /*
  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
  * any, so that if somebody tries to do "Save"
  * while there's already a "Save TCP Follow Stream" window up, we just pop
  * up the existing one, rather than creating a new one.
  */
-static GtkWidget *follow_save_as_w;
-
 static void
-follow_save_as_cmd_cb(GtkWidget *w, gpointer data)
+follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
 {
-  GtkWidget *ok_bt;
+    GtkWidget          *new_win;
+    follow_info_t      *follow_info = data;
 
-  if (follow_save_as_w != NULL) {
-    /* There's already a dialog box; reactivate it. */
-    reactivate_window(follow_save_as_w);
-    return;
-  }
-
-  follow_save_as_w = gtk_file_selection_new ("Ethereal: Save TCP Follow Stream As");
-  gtk_signal_connect(GTK_OBJECT(follow_save_as_w), "destroy",
-       GTK_SIGNAL_FUNC(follow_save_as_destroy_cb), NULL);
-
-  /* If we've opened a file, start out by showing the files in the directory
-     in which that file resided. */
-  if (last_open_dir)
-    gtk_file_selection_complete(GTK_FILE_SELECTION(follow_save_as_w), last_open_dir);
-
-  /* Connect the ok_button to file_save_as_ok_cb function and pass along a
-     pointer to the file selection box widget */
-  ok_bt = GTK_FILE_SELECTION (follow_save_as_w)->ok_button;
-  gtk_signal_connect(GTK_OBJECT (ok_bt), "clicked",
-    (GtkSignalFunc) follow_save_as_ok_cb, follow_save_as_w);
-
-  /* Connect the cancel_button to destroy the widget */
-  gtk_signal_connect_object(GTK_OBJECT (GTK_FILE_SELECTION
-    (follow_save_as_w)->cancel_button), "clicked", (GtkSignalFunc)
-    gtk_widget_destroy, GTK_OBJECT (follow_save_as_w));
-
-  /* Catch the "key_press_event" signal in the window, so that we can catch
-     the ESC key being pressed and act as if the "Cancel" button had
-     been selected. */
-  dlg_set_cancel(follow_save_as_w, GTK_FILE_SELECTION(follow_save_as_w)->cancel_button);
-
-  gtk_file_selection_set_filename(GTK_FILE_SELECTION(follow_save_as_w), "");
-  gtk_widget_show(follow_save_as_w);
+    if (follow_info->follow_save_as_w != NULL) {
+       /* There's already a dialog box; reactivate it. */
+       reactivate_window(follow_info->follow_save_as_w);
+       return;
+    }
+
+    new_win = file_selection_new("Ethereal: Save TCP Follow Stream As",
+                                 FILE_SELECTION_SAVE);
+    follow_info->follow_save_as_w = new_win;
+
+    /* Tuck away the follow_info object into the window */
+    OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info);
+
+    SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info);
+
+#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
+    if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
+    {
+        follow_save_as_ok_cb(new_win, new_win);
+    } else {
+        window_destroy(new_win);
+    }
+#else
+    /* Connect the ok_button to file_save_as_ok_cb function and pass along a
+       pointer to the file selection box widget */
+    SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button, 
+        "clicked", follow_save_as_ok_cb, new_win);
+
+    window_set_cancel_button(new_win, 
+        GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb);
+
+    gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
+
+    SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL);
+
+    gtk_widget_show_all(new_win);
+    window_present(new_win);
+#endif
 }
 
+
 static void
-follow_save_as_ok_cb(GtkWidget *w, GtkFileSelection *fs)
+follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
 {
-  gchar        *to_name;
+    gchar              *to_name;
+    follow_info_t      *follow_info;
+    FILE               *fh;
+    print_stream_t     *stream = NULL;
+    gchar              *dirname;
+
+#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
+    to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
+#else
+    to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
+#endif
 
-  to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
-  gtk_widget_hide(GTK_WIDGET (fs));
-  gtk_widget_destroy(GTK_WIDGET (fs));
+    /* Perhaps the user specified a directory instead of a file.
+       Check whether they did. */
+    if (test_for_directory(to_name) == EISDIR) {
+        /* It's a directory - set the file selection box to display that
+           directory, and leave the selection box displayed. */
+        set_last_open_dir(to_name);
+        g_free(to_name);
+        file_selection_set_current_folder(fs, get_last_open_dir());
+        return;
+    }
 
-  copy_binary_file(data_out_filename, to_name);
+    follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY);
+    if (follow_info->show_type == SHOW_RAW) {
+        /* Write the data out as raw binary data */
+        fh = eth_fopen(to_name, "wb");
+    } else {
+        /* Write it out as text */
+        fh = eth_fopen(to_name, "w");
+    }
+    if (fh == NULL) {
+        open_failure_alert_box(to_name, errno, TRUE);
+        g_free(to_name);
+        return;
+    }
 
-  g_free(to_name);
+    gtk_widget_hide(GTK_WIDGET(fs));
+    window_destroy(GTK_WIDGET(fs));
+
+    if (follow_info->show_type == SHOW_RAW) {
+        switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
+        case FRS_OK:
+            if (fclose(fh) == EOF)
+                write_failure_alert_box(to_name, errno);
+            break;
+
+        case FRS_OPEN_ERROR:
+        case FRS_READ_ERROR:
+            fclose(fh);
+            break;
+
+        case FRS_PRINT_ERROR:
+            write_failure_alert_box(to_name, errno);
+            fclose(fh);
+            break;
+        }
+    } else {
+       stream = print_stream_text_stdio_new(fh);
+       switch (follow_read_stream(follow_info, follow_print_text, stream)) {
+        case FRS_OK:
+            if (!destroy_print_stream(stream))
+                write_failure_alert_box(to_name, errno);
+            break;
+
+        case FRS_OPEN_ERROR:
+        case FRS_READ_ERROR:
+            destroy_print_stream(stream);
+            break;
+
+        case FRS_PRINT_ERROR:
+            write_failure_alert_box(to_name, errno);
+            destroy_print_stream(stream);
+            break;
+        }
+    }
+
+    /* Save the directory name for future file dialogs. */
+    dirname = get_dirname(to_name);  /* Overwrites to_name */
+    set_last_open_dir(dirname);
+    g_free(to_name);
 }
 
 static void
-follow_save_as_destroy_cb(GtkWidget *win, gpointer user_data)
+follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
 {
-  /* Note that we no longer have a dialog box. */
-  follow_save_as_w = NULL;
+       follow_info_t   *follow_info = data;
+
+       /* Note that we no longer have a dialog box. */
+       follow_info->follow_save_as_w = NULL;
 }