/*

Copyright (C) 2000 - 2004 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#ifdef LINUX
#define __FAVOR_BSD
#endif

#include <gtk/gtk.h>
#include <nd.h>
#include <nd_gui.h>
#include <nd_clipboard.h>
#include <nd_packet.h>
#include <nd_tp.h>
#include <nd_trace_registry.h>
#include <nd_protocol.h>
#include <nd_protocol_inst.h>
#include <nd_prefs.h>
#include <callbacks.h>
#include <interface.h>
#include <support.h>

#define STATUSBAR_TIMEOUT            5
#define GTK_CLIST_CLASS_FW(_widget_) GTK_CLIST_CLASS (((GtkObject*) (_widget_))->klass)
#define PBAR_ACTIVITY_BLOCKS         10


static GtkProgressBar *pbar;
static int             pbar_total;
static int             pbar_current;
static gboolean        pbar_activity_mode;
static int             pbar_timeout_set;
static gfloat          pbar_old_ratio = -1.0;

static GdkPixmap      *incomplete_pmap;
static GdkBitmap      *incomplete_mask;

static guint           timestamp_id;
static int             timestamp_set = FALSE;

static GdkColor        bg[5];
static GdkColor        red[5];
static GdkColor        yellow[5];

static GtkTooltips*    tt;

static GList          *monowidth_widgets = NULL;

static GtkWidget      *iterator_menu;

static gchar          *list_titles[2] = { N_("Tcpdump log"), N_("State") };

static void
gui_pbar_clear(void)
{
  nd_gui_pbar_clear(2000);
}


void    
nd_gui_init(void)
{
  int         i;
  GtkWidget  *win;
  GtkWidget  *w;
  GtkStyle   *gs;
  LND_PacketIteratorObserver *ob;

  D_ENTER;

  /* Hide the dummy entry in the protocols menu */
  ND_GTK_GET(w, nd_toplevel_window, "proto_dummy");
  gtk_widget_hide(w);

  /* No traces are loaded yet -- hide the traces notebook. */
  ND_GTK_GET(w, nd_toplevel_window, "traces_notebook");
  gtk_widget_hide(w);

  /* Initialize the global progress bar pointer */
  ND_GTK_GET(pbar, nd_toplevel_window, "progressbar");
  gtk_progress_bar_set_activity_blocks(pbar, PBAR_ACTIVITY_BLOCKS);

  /* Pixmap and Color stuff below */
  gs = gtk_widget_get_style(nd_toplevel_window);
  win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_realize(win);
  incomplete_pmap = gdk_pixmap_create_from_xpm(win->window, &incomplete_mask,
					       &gs->bg[GTK_STATE_NORMAL],
					       PACKAGE_DATA_DIR "/" VERSION_MAJOR "/pixmaps/incomplete.xpm");

  /* Init red and yellow: */
  for (i = 0; i < 5; i++)
    {
      bg[i] = gs->bg[i];
      red[i] = gs->bg[i];
      red[i].red = (2 * red[i].red) > 65535 ? 65535 : 2 * red[i].red;
      if (red[i].red == 0)
	red[i].red = 32768;
      red[i].green /=  2;
      red[i].blue /= 2;
    }

  for (i = 0; i < 5; i++)
    {
      yellow[i] = gs->bg[i];
      yellow[i].green = (2 * yellow[i].green) > 65535 ? 65535 : 2 * yellow[i].green;
      yellow[i].red = (2 * yellow[i].red) > 65535 ? 65535 : 2 * yellow[i].red;

      if (yellow[i].green == 0)
	yellow[i].green = 32768;

      if (yellow[i].red == 0)
	yellow[i].red = 32768;

      yellow[i].blue /=  2;
    }

  /* Create tooltips, if necessary. */
  if (! (tt = gtk_object_get_data(GTK_OBJECT(nd_toplevel_window), "tooltips")))
    {
      tt = gtk_tooltips_new();
      gtk_object_set_data (GTK_OBJECT (nd_toplevel_window), "tooltips", tt);
      gtk_tooltips_enable(tt);
    }

  /* Initialize the view indicator */
  nd_gui_update_view_indicator();
  nd_gui_update_area_indicator();

  /* Initialize the progress bar hooks from libnetdude, so that the GUI updates
   * the progress bar whenever libnetdude is iterating over packets.
   */
  if ( (ob = libnd_pit_observer_new()))
    {
      ob->pit_init     = nd_gui_pbar_reset;
      ob->pit_progress = nd_gui_pbar_inc;
      ob->pit_clear    = gui_pbar_clear;

      libnd_pit_add_observer(ob);
    }

  /* Initialize the debugging menu -- if compiled with debugging, show
   * the menu, otherwise hide it.
   */
#ifdef ND_DEBUG
  ND_GTK_GET(w, nd_toplevel_window, "netdude_output");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), nd_runtime_options.debug);
  ND_GTK_GET(w, nd_toplevel_window, "libnetdude_output");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), libnd_runtime_options.debug);
  ND_GTK_GET(w, nd_toplevel_window, "pcapnav_output");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), pcapnav_runtime_options.debug);
#else
  ND_GTK_GET(w, nd_toplevel_window, "debugging");
  gtk_widget_hide(w);
#endif

  D_RETURN;
}


void      
nd_gui_sync_edit_menu(void)
{
  LND_Trace  *trace   = NULL;
  LND_Packet *current = NULL;
  GtkWidget  *button;
  gboolean has_packets = FALSE;

  trace = nd_trace_registry_get_current();
  if (trace)
    {
      current = nd_trace_get_current_packet(trace);
      has_packets = (trace->tpm->current->pl != NULL);
    }

  ND_GTK_GET(button, nd_toplevel_window, "packet_paste");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (nd_clipboard_occupied() && trace));  
  ND_GTK_GET(button, nd_toplevel_window, "packet_delete");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_copy");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_cut");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_select_all");
  gtk_widget_set_sensitive(GTK_WIDGET(button), ((trace && has_packets) ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_unselect_all");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current && libnd_tp_get_sel_size(trace->tpm->current) > 0));
  ND_GTK_GET(button, nd_toplevel_window, "filtering");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (trace? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_filter");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "packet_unfilter");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "trace_areas");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (trace? TRUE : FALSE));
}


void      
nd_gui_sync_go_menu(void)
{
  LND_Trace  *trace   = NULL;
  LND_Packet *current = NULL;
  GtkWidget  *button;

  trace = nd_trace_registry_get_current();

  if (trace)
    current = nd_trace_get_current_packet(trace);
  
  ND_GTK_GET(button, nd_toplevel_window, "next_packet");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "previous_packet");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "next_selected");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "previous_selected");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "next_with_same_protocol");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "previous_with_same_protocol");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "next_unfiltered");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "previous_unfiltered");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (current ? TRUE : FALSE));
  ND_GTK_GET(button, nd_toplevel_window, "other_trace_area");
  gtk_widget_set_sensitive(GTK_WIDGET(button), (trace && trace->needs_nav ? TRUE : FALSE));
}


void      
nd_gui_sync_file_menu(void)
{
  GtkWidget *button;
  gboolean   enable;
  
  enable = nd_trace_registry_get_current() ? TRUE : FALSE;

  ND_GTK_GET(button, nd_toplevel_window, "save");
  gtk_widget_set_sensitive(GTK_WIDGET(button), enable);
  ND_GTK_GET(button, nd_toplevel_window, "save_as");
  gtk_widget_set_sensitive(GTK_WIDGET(button), enable);
  ND_GTK_GET(button, nd_toplevel_window, "info");
  gtk_widget_set_sensitive(GTK_WIDGET(button), enable);
  ND_GTK_GET(button, nd_toplevel_window, "close");
  gtk_widget_set_sensitive(GTK_WIDGET(button), enable);
}

static void
gui_sync_protocol_cb(LND_Protocol *proto, void *user_data)
{
  ND_Protocol *proto_gui = nd_proto_get(proto);

  if (!proto_gui || !proto_gui->proto_menu_item)
    return;

  gtk_widget_set_sensitive(proto_gui->proto_menu_item, user_data ? TRUE : FALSE);  
}

void      
nd_gui_sync_protocol_menu(void)
{
  LND_Trace *trace;

  trace = nd_trace_registry_get_current();
  libnd_proto_registry_foreach_proto(gui_sync_protocol_cb, trace);
}

void
nd_gui_sync_menus(void)
{
  D_ENTER;
  nd_gui_sync_edit_menu();
  nd_gui_sync_file_menu();
  nd_gui_sync_go_menu();
  nd_gui_sync_protocol_menu();
  D_RETURN;
}


void         
nd_gui_show_packet_menu(GdkEventButton *event)
{
  GtkWidget *menu;

  /* Let's grab the edit menu from the main window: */
  ND_GTK_GET(menu, nd_toplevel_window, "edit_menu");
  
  /* Update its settings -- everything is registered with the
     nd_toplevel_window window: */
  nd_gui_sync_edit_menu();

  /* And show the menu :) */
  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
		 event->button, event->time);
}


static void
iterator_menu_position_func(GtkMenu   *menu,
			    gint      *x,
			    gint      *y,
			    gpointer   user_data)
{
  LND_Trace *trace = (LND_Trace *) user_data;
  ND_Trace *trace_gui;
  GtkWidget *button;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  ND_GTK_GET(button, trace_gui->tab, "apply_button");
  gdk_window_get_deskrelative_origin(button->window, x, y);
  *y += button->allocation.height;

  return;
  TOUCH(menu);
}


void
nd_gui_show_iterator_mode_menu(GdkEventButton *event)
{
  LND_Trace *trace;

  return_if_no_current_trace(trace);

  if (!iterator_menu)
    iterator_menu = create_trace_menu();
    
  nd_gui_iterator_mode_set(trace);

  gtk_menu_popup(GTK_MENU(iterator_menu), NULL, NULL,
		 iterator_menu_position_func, trace,
		 event->button, event->time);
}


/* Unused code -- this used to be to avoid Gtk CList overhead, but since 
 * the lists should not be long any more now that we only load a smallish
 * number of packets, this hack is no longer needed ... :)

static void      
gui_gtk_clist_select_all(GtkCList *clist)
{
  int i;
  GtkCListRow *clist_row;
  GList *row_list;
  
  D_ASSERT_PTR(clist);

  if (!clist)
    return;

  row_list = clist->row_list;

  for (i = 0; row_list; i++)
    {
      clist_row = row_list->data;
      clist_row->state = GTK_STATE_SELECTED;
      if (gtk_clist_row_is_visible (clist, i) != GTK_VISIBILITY_NONE)
	GTK_CLIST_CLASS_FW(clist)->draw_row (clist, NULL, i, clist_row);
      if (!clist->selection)
	{
	  clist->selection = g_list_append (clist->selection, GINT_TO_POINTER(i));
	  clist->selection_end = clist->selection;
	}
      else
	clist->selection_end = 
	  g_list_append (clist->selection_end, GINT_TO_POINTER(i))->next;
      row_list = row_list->next;
    }

  on_trace_list_select_all(clist, NULL);
}


static void
gui_gtk_clist_unselect_all(GtkCList *clist)
{
  int i;
  GtkCListRow *clist_row;
  GList *row_list;
  
  D_ASSERT_PTR(clist);
  if (!clist)
    return;

  row_list = clist->row_list;

  for (i = 0; row_list; i++)
    {
      clist_row = row_list->data;
      if (clist_row->state == GTK_STATE_SELECTED)
	{
	  clist_row->state = GTK_STATE_NORMAL;
	  if (gtk_clist_row_is_visible (clist, i) != GTK_VISIBILITY_NONE)
	    GTK_CLIST_CLASS_FW(clist)->draw_row (clist, NULL, i, clist_row);
	}
      row_list = row_list->next;
    }

  g_list_free(clist->selection);
  clist->selection = NULL;
  clist->selection_end = NULL;

  on_trace_list_unselect_all(clist, NULL);
}
*/


void    
nd_gui_num_packets_set(void)
{
  LND_Trace       *trace;
  char             s[MAXPATHLEN];
  GtkWidget       *w;

  trace = nd_trace_registry_get_current();

  if (!trace)
    s[0] = '\0';
  else if (trace->tpm->current->num_packets == 1)
    g_snprintf(s, MAXPATHLEN, _("1 packet."));
  else
    g_snprintf(s, MAXPATHLEN, _("%i packets."),
	       trace->tpm->current->num_packets);
  
  ND_GTK_GET(w, nd_toplevel_window, "num_packets_label");
  gtk_label_set_text(GTK_LABEL(w), s);
}


void         
nd_gui_set_trace_status(LND_Trace *trace)
{
  GtkWidget *button;
  GtkWidget *pixmap_red, *pixmap_green;
  ND_Trace  *trace_gui;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  ND_GTK_GET(button, trace_gui->tab, "close_button");
  ND_GTK_GET(pixmap_green, trace_gui->tab, "pixmap_green");
  ND_GTK_GET(pixmap_red, trace_gui->tab, "pixmap_red");

  if (pixmap_green->parent)
    gtk_container_remove(GTK_CONTAINER(button), pixmap_green);

  if (pixmap_red->parent)
    gtk_container_remove(GTK_CONTAINER(button), pixmap_red);

  if (trace->dirty)
    gtk_container_add(GTK_CONTAINER(button), pixmap_red);
  else
    gtk_container_add(GTK_CONTAINER(button), pixmap_green);
}


void         
nd_gui_iterator_mode_set(LND_Trace *trace)
{
  GtkWidget *button, *w;
  GtkWidget *pixmap_sel, *pixmap_part, *pixmap_all;
  ND_Trace *trace_gui = nd_trace_get(trace);

  if (!trace_gui)
    return;

  if (!iterator_menu)
    iterator_menu = create_trace_menu();

  ND_GTK_GET(button, trace_gui->tab, "apply_button");
  ND_GTK_GET(pixmap_sel,  trace_gui->tab, "pixmap_apply_sel");
  ND_GTK_GET(pixmap_part, trace_gui->tab, "pixmap_apply_part");
  ND_GTK_GET(pixmap_all,  trace_gui->tab, "pixmap_apply_all");

  if (pixmap_sel->parent)
    gtk_container_remove(GTK_CONTAINER(button), pixmap_sel);
  if (pixmap_part->parent)
    gtk_container_remove(GTK_CONTAINER(button), pixmap_part);
  if (pixmap_all->parent)
    gtk_container_remove(GTK_CONTAINER(button), pixmap_all);

  switch(trace->iterator_mode)
    {
    case LND_PACKET_IT_SEL_R:
    case LND_PACKET_IT_SEL_RW:
      ND_GTK_GET(w, iterator_menu, "apply_to_selection");
      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), TRUE);
      gtk_container_add(GTK_CONTAINER(button), pixmap_sel);
      break;

    default:
      ND_GTK_GET(w, iterator_menu, "apply_to_trace_area");
      gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), TRUE);
      gtk_container_add(GTK_CONTAINER(button), pixmap_part);
    }
}


static void
gui_statusbar_set(const char *text, gboolean clear)
{
  static guint timeout_id;
  GtkLabel *l;

  if (!text)
    return;

  ND_GTK_GET(l, nd_toplevel_window, "statuslabel");
  gtk_label_set_text(l, text);

  while (gtk_events_pending())
    gtk_main_iteration();

  if (pbar_timeout_set)
    gtk_timeout_remove(timeout_id);

  if (clear)
    {
      timeout_id = gtk_timeout_add(STATUSBAR_TIMEOUT * 1000, nd_gui_statusbar_clear, NULL);
      pbar_timeout_set = TRUE;
    }
}


void      
nd_gui_statusbar_set(const char *text)
{
  gui_statusbar_set(text, TRUE);
}


void      
nd_gui_statusbar_set_noclear(const char *text)
{
  gui_statusbar_set(text, FALSE);
}


gint
nd_gui_statusbar_clear(gpointer data)
{
  GtkLabel *l;

  ND_GTK_GET(l, nd_toplevel_window, "statuslabel");
  gtk_label_set_text(l, "");
  pbar_timeout_set = 0;

  return 0;
  data = NULL;
}


void      
nd_gui_pbar_reset(int num_total)
{
  LND_Trace *trace;
  pbar_current = 0;
  
  D_ENTER;

  if (num_total > 0)
    {
      D(("Progressbar initialized to %i steps.\n", num_total));
      pbar_total = num_total;
    }
  else
    {
      if ( (trace = nd_trace_registry_get_current()))
	{
	  switch (trace->iterator_mode)
	    {
	    case LND_PACKET_IT_PART_R:
	    case LND_PACKET_IT_PART_RW:
	      pbar_total = trace->tpm->current->num_packets;
	      break;
	      
	    case LND_PACKET_IT_AREA_R:
	    case LND_PACKET_IT_AREA_RW:
	      pbar_total = trace->tpm->size;
	      break;

	    case LND_PACKET_IT_SEL_R:
	    case LND_PACKET_IT_SEL_RW:
	    default:
	      pbar_total = libnd_tp_get_sel_size(trace->tpm->current);
	    }
	}
    }

  nd_gui_pbar_clear(0);

  D_RETURN;
}


void      
nd_gui_pbar_inc(int num_inc)
{
  gfloat ratio;

  /* D_ENTER; */

  pbar_current += num_inc;

  if (pbar_current > pbar_total)
    pbar_current = pbar_total;

  if ( (ratio = (gfloat)pbar_current/(gfloat)pbar_total) > pbar_old_ratio + 0.01)
    {
      gtk_progress_bar_update(pbar, MIN(ratio, 1.0));

      while (gtk_events_pending())
	gtk_main_iteration();

      pbar_old_ratio = ratio;
    }

  /* D_RETURN; */
}


static gint 
gui_pbar_clear_timeout(gpointer data)
{
  gtk_progress_bar_update(pbar, 0.0);
  pbar_old_ratio = -1.0;

  while (gtk_events_pending())
    gtk_main_iteration();

  return FALSE;
  TOUCH(data);
}

void      
nd_gui_pbar_clear(int delay)
{
  gtk_progress_bar_update(pbar, 0.0);
  pbar_old_ratio = -1.0;

  gtk_timeout_add(delay, gui_pbar_clear_timeout, NULL);
}

static gint 
gui_pbar_timeout(gpointer data)
{
  static guint step = 0;

  if (step > PBAR_ACTIVITY_BLOCKS)
    step = 0;

  gtk_progress_set_value(GTK_PROGRESS(pbar), step++);

  while (gtk_events_pending())
    gtk_main_iteration();

  if (pbar_activity_mode)
    return (TRUE); /* Let's get called again */

  nd_gui_pbar_clear(0);

  return (FALSE);
  data = NULL;
}


void      
nd_gui_pbar_start_activity(void)
{
  gtk_progress_set_activity_mode(GTK_PROGRESS(pbar), TRUE);
  pbar_activity_mode = TRUE;
  gtk_timeout_add(100, gui_pbar_timeout, NULL);
}


void      
nd_gui_pbar_stop_activity(void)
{
  gtk_progress_set_activity_mode(GTK_PROGRESS(pbar), FALSE);
  pbar_activity_mode = FALSE;
}


void
nd_gui_list_freeze(LND_Trace *trace)
{
  ND_Trace        *trace_gui;

  if (!trace || !trace->tpm || !trace->tpm->current)
    D_RETURN;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  gtk_clist_freeze(GTK_CLIST(trace_gui->list));
}


void
nd_gui_list_thaw(LND_Trace *trace)
{
  ND_Trace        *trace_gui;

  if (!trace || !trace->tpm || !trace->tpm->current)
    D_RETURN;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  gtk_clist_thaw(GTK_CLIST(trace_gui->list));
}

void      
nd_gui_list_set_incomplete_column_visible(LND_Trace *trace, gboolean visible)
{
  ND_Trace *trace_gui;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  gtk_clist_set_column_visibility(GTK_CLIST(trace_gui->list), 1, visible);
}


void      
nd_gui_list_set_row_incomplete(LND_Trace *trace, int row, gboolean incomplete)
{
  ND_Trace *trace_gui;
  GtkCList *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);

  if (incomplete)
    gtk_clist_set_pixmap(clist, row, 1, incomplete_pmap, incomplete_mask);
  else
    gtk_clist_set_text(clist, row, 1, "");
}


void      
nd_gui_list_set_row_filtered(LND_Trace *trace, int row, gboolean filtered)
{
  ND_Trace *trace_gui;
  GtkCList *clist;
  GtkStyle *gs;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gs = gtk_widget_get_style(GTK_WIDGET(clist));

  if (filtered)
    gtk_clist_set_foreground(clist, row, gs->mid);
  else
    gtk_clist_set_foreground(clist, row, gs->text);
}


void      
nd_gui_set_windowtitle(const char *filename)
{
  char  s[MAXPATHLEN];
  char *result = (char *) filename;
  int   show_full_path;

  D_ENTER;

  if (!filename)
    {
      D(("Input error.\n"));
      D_RETURN;
    }

  libnd_prefs_get_int_item(ND_DOM_NETDUDE, "show_full_path", &show_full_path);
  
  if (!show_full_path)
    result = g_basename(filename);
  
  g_snprintf(s, MAXPATHLEN, "Netdude: %s", result);
  gtk_window_set_title(GTK_WINDOW(nd_toplevel_window), s);
}


static gint
gui_show_timestamp_timeout(gpointer data)
{
  GtkWidget *w;

  ND_GTK_GET(w, nd_toplevel_window, "timestamp_win");

  gtk_widget_show(w);

  return (0);
  data = NULL;
}


void         
nd_gui_timestamp_schedule(GtkCList *clist,
			  GdkEventMotion *event)
{
  static GtkWidget *tooltip = NULL;

  LND_Packet *packet;
  LND_Trace  *trace;
  char        ts_text[MAXPATHLEN];
  int         ptr_x, ptr_y, row;
  int         pref_show_ts, pref_show_ts_abs;
  float       pref_ts_delay; 
  GtkWidget  *ts_label;


  /* We won't show timestamps if any buttons are pressed
     while moving around ... */
  
  if ((event->state & (GDK_BUTTON1_MASK |
		       GDK_BUTTON2_MASK |
		       GDK_BUTTON3_MASK |
		       GDK_BUTTON4_MASK |
		       GDK_BUTTON5_MASK)) != 0)
    return;

  row = (event->y - clist->voffset) / (clist->row_height + 1);

  trace  = nd_trace_registry_get_current();
  D_ASSERT_PTR(trace);
  packet = libnd_tp_packet_get_nth(trace->tpm->current, row);

  if (!packet)
    return;

  pref_show_ts = TRUE;
  pref_show_ts_abs = FALSE;
  pref_ts_delay = 0.5; 

  libnd_prefs_get_int_item(ND_DOM_NETDUDE, "show_timestamps", &pref_show_ts);
  libnd_prefs_get_int_item(ND_DOM_NETDUDE, "show_timestamps_absolute", &pref_show_ts_abs);
  libnd_prefs_get_flt_item(ND_DOM_NETDUDE, "timestamps_delay", &pref_ts_delay);

  if (!pref_show_ts)
    return;

  if (tooltip)
    gtk_widget_hide(tooltip);

  if (timestamp_set && pref_ts_delay > 0.1)
    gtk_timeout_remove(timestamp_id);
  
  if (packet->prev && !pref_show_ts_abs)
    {
      struct bpf_timeval delta_last, delta_abs;
      
      TV_SUB(&packet->ph.ts, &packet->prev->ph.ts, &delta_last);
      TV_SUB(&packet->ph.ts, &trace->tpm->current->start_ts, &delta_abs);
      
      g_snprintf(ts_text, MAXPATHLEN, "%li.%06lis, %li.%06lis (%lu.%lu)",
		 (long int) delta_abs.tv_sec, (long int) delta_abs.tv_usec,
		 (long int) delta_last.tv_sec, (long int) delta_last.tv_usec,
		 (long unsigned int) packet->ph.ts.tv_sec, (long unsigned int) packet->ph.ts.tv_usec);
    }
  else
    {
      int offset;
      time_t tt = (time_t)packet->ph.ts.tv_sec;
      
      g_snprintf(ts_text, MAXPATHLEN, "%s", ctime(&tt));
      offset = strlen(ts_text)-1;
      g_snprintf(ts_text + offset, MAXPATHLEN, " +  %li usec (%lu.%lu)",
		 (long int) packet->ph.ts.tv_usec,
		 (long unsigned int) packet->ph.ts.tv_sec, (long unsigned) packet->ph.ts.tv_usec);
    }
  
  /* If the packet is only partly captured, show
     how many bytes are missing: */
  if (packet->ph.len > packet->ph.caplen)
    {
      int offset = strlen(ts_text);
      
      g_snprintf(ts_text + offset, MAXPATHLEN - offset,
		 _(", %i bytes missing"), 
		 packet->ph.len - packet->ph.caplen);
    }

  if (!tooltip)
    {
      tooltip = create_timestamp_window();
      gtk_window_set_position(GTK_WINDOW(tooltip), GTK_WIN_POS_NONE);
      gtk_object_set_data(GTK_OBJECT(nd_toplevel_window), "timestamp_win", tooltip);
    }
    
  ND_GTK_GET(ts_label, tooltip, "timestamp_label");
  gtk_label_set_text(GTK_LABEL(ts_label), ts_text);
  
  /* HELP! What's the correct way to ensure that w->allocation.width has the correct
     size now? I haven't been able to figure it out. Since our window's content is
     simple (just the line of text), let's use the width of the text string as
     an approximation.
  */
  
  gtk_widget_realize(tooltip);
  
  gdk_window_get_pointer(NULL, &ptr_x, &ptr_y, NULL);
  gtk_widget_set_uposition(GTK_WIDGET(tooltip),
			   ptr_x - gdk_text_width(ts_label->style->font, ts_text, strlen(ts_text))/2,
			   ptr_y + 15);      
  

  timestamp_id = gtk_timeout_add(pref_ts_delay * 1000,
				 gui_show_timestamp_timeout, NULL);
  timestamp_set = TRUE;
}


void        
nd_gui_timestamp_hide(void)
{
  GtkWidget *tooltip;

  if (timestamp_set)
    {
      gtk_timeout_remove(timestamp_id);
      timestamp_set = FALSE;
    }

  tooltip = gtk_object_get_data(GTK_OBJECT(nd_toplevel_window), "timestamp_win");
  if (tooltip)
    gtk_widget_hide(tooltip);
}


static gint
gui_selection_descend_sort(gconstpointer a, gconstpointer b)
{
  if (GPOINTER_TO_UINT(a) < GPOINTER_TO_UINT(b))
    return 1;
  if (GPOINTER_TO_UINT(a) > GPOINTER_TO_UINT(b))
    return -1;

  return 0;
}


void      
nd_gui_list_remove_selected_rows(LND_Trace *trace)
{  
  ND_Trace  *trace_gui;
  GList     *l, *l2 = NULL, *l3 = NULL;
  GtkCList  *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gtk_signal_handler_block_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
  gtk_clist_freeze(clist);

  if (clist->selection)
    {      
      for (l = clist->selection; l; l = l->next)
	l2 = g_list_append(l2, l->data);
      
      l2 = g_list_sort(l2, gui_selection_descend_sort);
      
      for (l3 = l2 ; l3; l3 = l3->next)
	gtk_clist_remove(clist, GPOINTER_TO_UINT(l3->data));
    }

  gtk_clist_thaw(clist);
  gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
  
  if (l2)
    g_list_free(l2);
}


void
nd_gui_list_remove_row(LND_Trace *trace, guint index)
{
  ND_Trace  *trace_gui;
  GtkCList  *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;


  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gtk_signal_handler_block_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
  gtk_clist_freeze(clist);
  gtk_clist_remove(clist, index);
  gtk_clist_thaw(clist);
  gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
}


void
nd_gui_list_insert_row(LND_Packet *packet, int index)
{
  ND_Trace  *trace_gui;
  char       line[MAXPATHLEN];
  char      *s[2];
  
  if (!packet || index < 0 || !packet->part)
    return;

  if (! (trace_gui = nd_trace_get(libnd_packet_get_trace(packet))))
    return;

  if (! libnd_tcpdump_get_packet_line(packet, line, MAXPATHLEN, TRUE))
    {
      g_snprintf(line, MAXPATHLEN, "[tcpdump I/O error]");
      nd_dialog_message(_("Tcpdump I/O error"),
			_("An error occurred when obtaining tcpdump packet output.\n"),
			TRUE);
    }

  s[0] = line;
  s[1] = "";

  gtk_clist_insert(GTK_CLIST(trace_gui->list), index, s);
}


void         
nd_gui_list_clear(LND_Trace *trace)
{
  ND_Trace  *trace_gui;
  GtkCList  *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gtk_signal_handler_block_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
  gtk_clist_freeze(clist);
  gtk_clist_clear(clist);
  gtk_clist_thaw(clist);
  gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), on_trace_list_unselect_row, NULL);
}


static gboolean      
gui_list_update_packet(const LND_Packet *packet, char *line, int len)
{
  int do_magic = 0;
  guchar *raw_start, *raw_end;
  
  if (! libnd_tcpdump_get_packet_line(packet, line, len, TRUE))
    {
      nd_dialog_message(_("Tcpdump I/O error"),
			_("An error occurred when obtaining tcpdump packet output.\n"
			  "Not all packet ouput has been updated."),
			TRUE);
      return FALSE;
    }
  
  libnd_prefs_get_int_item(ND_DOM_NETDUDE, "show_magic_result", &do_magic);
  
  if (!do_magic)
    return TRUE;
  
  raw_start = libnd_packet_get_data(packet, libnd_raw_proto_get(), 0);
  raw_end = libnd_packet_get_data_end(packet, libnd_raw_proto_get(), 0);

  if (raw_start && raw_end)
    {
      char magicstr[512];
      guchar *raw_start2 = NULL;
      guchar *sep = strstr(raw_start, "\r\n\r\n");
      
      if (sep && sep + 4 != raw_end)
	{
	  char *m1, *m2;
	  raw_start2 = sep + 4;
	  
	  m1 = strdup(libnd_magic_buffer(raw_start, raw_start2 - raw_start));
	  m2 = strdup(libnd_magic_buffer(raw_start2, raw_end - raw_start2));
	  
	  if (m1[0] != '\0' || m2[0] != '\0')
	    {
	      snprintf(magicstr, 512, " [%s%s%s]",
		       m1[0] != '\0' ? m1 : "",
		       m1[0] != '\0' && m2[0] != '\0' ? "/" : "",
		       m2[0] != '\0' ? m2 : "");
	      magicstr[511] = '\0';
	      strncat(line, magicstr, len - strlen(line));
	      line[len-1] = '\0';
	    }
	  
	  g_free(m1);
	  g_free(m2);
	}
      else
	{
	  const char *magic = libnd_magic_buffer(raw_start, raw_end - raw_start);
	  
	  if (magic[0] != '\0')
	    {
	      snprintf(magicstr, 512, " [%s]", magic);
	      magicstr[511] = '\0';
	      strncat(line, magicstr, len - strlen(line));
	      line[len-1] = '\0';
	    }
	}
    }
  
  return TRUE;
}


void    
nd_gui_list_update(LND_Trace *trace)
{
  ND_Trace   *trace_gui;
  GtkCList   *clist;
  LND_Packet *packet;
  char        line[MAXPATHLEN];
  char       *s[2];
  int         i;

  D_ENTER;

  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  if (!trace_gui->list)
    D_RETURN;

  clist = GTK_CLIST(trace_gui->list);
  gtk_clist_freeze(clist);
  gtk_clist_clear(clist);    
  gtk_clist_set_reorderable(clist, 1);
  
  nd_gui_statusbar_set(_("Updating tcpdump output..."));
  nd_gui_pbar_reset(trace->tpm->current->num_packets);
  
  s[0] = line;
  s[1] = "";
  
  for (packet = libnd_tpm_get_packets(trace->tpm), i = 0; packet; packet = packet->next, i++)
    {
      if (! gui_list_update_packet(packet, line, MAXPATHLEN))
	break;
      
      gtk_clist_append(clist, s);      
      nd_gui_list_set_row_incomplete(trace, i, ! libnd_packet_is_complete(packet));
      nd_gui_list_set_row_filtered(libnd_packet_get_trace(packet), i, libnd_packet_is_filtered(packet));      
      nd_gui_pbar_inc(1);
    }
  
  nd_gui_pbar_clear(0);    
  gtk_clist_thaw(clist);
  
  D_RETURN;
}


void        
nd_gui_list_update_packet(const LND_Packet *packet)
{
  ND_Trace    *trace_gui;
  int          index;
  char         line[MAXPATHLEN];

  if (!packet || !packet->part)
    return;

  if (! (trace_gui = nd_trace_get(libnd_packet_get_trace(packet))))
    return;

  index = libnd_packet_get_index(packet);
  gui_list_update_packet(packet, line, MAXPATHLEN);
  gtk_clist_set_text(GTK_CLIST(trace_gui->list), index, 0, line);
  
  nd_gui_list_set_row_incomplete(libnd_packet_get_trace(packet), index,
				 ! libnd_packet_is_complete(packet));
}


void        
nd_gui_list_update_packet_at_index(const LND_Packet *p, int index)
{
  ND_Trace    *trace_gui;
  char         line[MAXPATHLEN];

  if (!p)
    return;

  if (! (trace_gui = nd_trace_get(libnd_packet_get_trace(p))))
    return;

  if (index < 0)
    {
      nd_gui_list_update_packet(p);
      return;
    }    

  D_ASSERT_PTR(p->part);
  D_ASSERT_PTR(trace_gui->list);

  if (! libnd_tcpdump_get_packet_line(p, line, MAXPATHLEN, TRUE))
    {
      g_snprintf(line, MAXPATHLEN, "[tcpdump I/O error]");
      nd_dialog_message(_("Tcpdump I/O error"),
			_("An error occurred when obtaining tcpdump packet output.\n"),
			TRUE);
    }
  
  gtk_clist_set_text(GTK_CLIST(trace_gui->list), index, 0, line);
  
  nd_gui_list_set_row_incomplete(libnd_packet_get_trace(p), index,
				 ! libnd_packet_is_complete(p));
}


void
nd_gui_list_select_packet(LND_Trace *trace, guint index)
{
  ND_Trace *trace_gui;
  GtkCList *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gtk_clist_select_row(clist, index, 0);  

  if (gtk_clist_row_is_visible(clist, index) != GTK_VISIBILITY_FULL)
    gtk_clist_moveto(clist, index, 0, 0.5, 0);
}


void
nd_gui_list_unselect_packet(LND_Trace *trace, guint index)
{
  ND_Trace *trace_gui;
  GtkCList *clist;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  if (!trace_gui->list)
    return;

  clist = GTK_CLIST(trace_gui->list);
  gtk_clist_unselect_row(clist, index, 0);  
}


void
nd_gui_list_unselect_all(LND_Trace *trace, gboolean emit_signal)
{
  ND_Trace *trace_gui;
  GtkCList *clist;

  D_ENTER;

  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  if (! trace_gui->list)
    D_RETURN;
  
  clist = GTK_CLIST(trace_gui->list);
  if (! emit_signal)
    gtk_signal_handler_block_by_func(GTK_OBJECT(clist), on_trace_list_unselect_all, NULL);
  gtk_clist_unselect_all(clist);  
  if (! emit_signal)
    gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), on_trace_list_unselect_all, NULL);

  D_RETURN;
}


void
nd_gui_list_select_all(LND_Trace *trace, gboolean emit_signal)
{
  ND_Trace *trace_gui;
  GtkCList *clist;

  D_ENTER;

  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  if (!trace_gui->list)
    D_RETURN;
  
  clist = GTK_CLIST(trace_gui->list);
  if (! emit_signal)
    gtk_signal_handler_block_by_func(GTK_OBJECT(clist), on_trace_list_select_all, NULL);
  gtk_clist_select_all(clist);  
  if (! emit_signal)
    gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), on_trace_list_select_all, NULL);

  D_RETURN;
}


static void
gui_update_line_cb(LND_Packet *packet,
		   LND_ProtoData *pd,
		   void *user_data)
{
  char *line = (char *) user_data;

  if (!packet || !pd)
    return;
  
  if (pd->inst.proto->is_stateful)
    pd->inst.proto->update_tcpdump_line(packet, line);
}


void         
nd_gui_list_update_packet_state(const LND_Packet *packet)
{
  ND_Trace    *trace_gui;
  int          index;
  char        *line;
  char         buf[MAXPATHLEN];

  if (!packet)
    return;

  if (! (trace_gui = nd_trace_get(libnd_packet_get_trace(packet))))
    return;

  D_ASSERT_PTR(trace_gui->list);
  
  index = libnd_packet_get_index(packet);

  gtk_clist_get_text(GTK_CLIST(trace_gui->list), index, 0, &line);
  g_snprintf(buf, MAXPATHLEN, line);
  
  libnd_packet_foreach_proto((LND_Packet *) packet,
			     gui_update_line_cb,
			     buf);

  gtk_clist_set_text(GTK_CLIST(trace_gui->list), index, 0, buf);
}


void         
nd_gui_list_update_packet_state_at_index(const LND_Packet *packet, int index)
{
  ND_Trace    *trace_gui;
  char        *line = NULL;
  char         buf[MAXPATHLEN];

  if (!packet)
    return;

  if (index < 0)
    {
      nd_gui_list_update_packet_state(packet);
      return;
    }    

  if (! (trace_gui = nd_trace_get(libnd_packet_get_trace(packet))))
    return;

  D_ASSERT_PTR(trace_gui->list);

  gtk_clist_get_text(GTK_CLIST(trace_gui->list), index, 0, &line);
  if (line)
    {
      g_snprintf(buf, MAXPATHLEN, line);
      
      libnd_packet_foreach_proto((LND_Packet *) packet,
				 gui_update_line_cb,
				 buf);
      
      gtk_clist_set_text(GTK_CLIST(trace_gui->list), index, 0, buf);
    }
}


static GtkWidget   *
gui_new_trace_list(void)
{
  GtkWidget *trace_list;

  trace_list = gtk_clist_new_with_titles(2, list_titles);

  /*
  GTK_CLIST_CLASS_FW(GTK_CLIST(trace_list))->select_all = gui_gtk_clist_select_all;
  GTK_CLIST_CLASS_FW(GTK_CLIST(trace_list))->unselect_all = gui_gtk_clist_unselect_all;
  */
  gtk_clist_set_column_justification(GTK_CLIST(trace_list), 1, GTK_JUSTIFY_CENTER);
  gtk_clist_set_column_resizeable(GTK_CLIST(trace_list), 0, FALSE);
  gtk_clist_set_column_visibility(GTK_CLIST(trace_list), 1, FALSE);
  gtk_clist_set_reorderable(GTK_CLIST(trace_list), TRUE);
  gtk_clist_set_use_drag_icons(GTK_CLIST(trace_list), FALSE);

  gtk_widget_show (trace_list);
  gtk_widget_set_events (trace_list, GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
  gtk_clist_set_column_width (GTK_CLIST (trace_list), 1, 80);
  gtk_clist_set_selection_mode (GTK_CLIST (trace_list), GTK_SELECTION_EXTENDED);
  gtk_clist_column_titles_show (GTK_CLIST (trace_list));
  gtk_clist_set_shadow_type (GTK_CLIST (trace_list), GTK_SHADOW_NONE);

  nd_gui_add_monowidth_widget(trace_list);

  gtk_clist_column_titles_passive(GTK_CLIST(trace_list));

  gtk_signal_connect (GTK_OBJECT (trace_list), "select_row",
                      GTK_SIGNAL_FUNC (on_trace_list_select_row),
                      NULL);
  gtk_signal_connect_after (GTK_OBJECT (trace_list), "row_move",
			    GTK_SIGNAL_FUNC (on_trace_list_row_move),
			    NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "button_press_event",
                      GTK_SIGNAL_FUNC (on_trace_list_button_press_event),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "unselect_row",
                      GTK_SIGNAL_FUNC (on_trace_list_unselect_row),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "end_selection",
                      GTK_SIGNAL_FUNC (on_trace_list_end_selection),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "select_all",
                      GTK_SIGNAL_FUNC (on_trace_list_select_all),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "unselect_all",
                      GTK_SIGNAL_FUNC (on_trace_list_unselect_all),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "size_allocate",
                      GTK_SIGNAL_FUNC (on_trace_list_size_allocate),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "motion_notify_event",
                      GTK_SIGNAL_FUNC (on_trace_list_motion_notify_event),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "leave_notify_event",
                      GTK_SIGNAL_FUNC (on_trace_list_leave_notify_event),
                      NULL);
  gtk_signal_connect (GTK_OBJECT (trace_list), "key_press_event",
                      GTK_SIGNAL_FUNC (on_trace_list_key_press_event),
                      NULL);

  return trace_list;
}


static GtkWidget   *
gui_new_trace_proto_notebook(void)
{
  GtkWidget *notebook;

  notebook = gtk_notebook_new ();
  gtk_widget_show (notebook);
  gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
  gtk_widget_set_usize (notebook, -2, 180);
  gtk_container_set_border_width (GTK_CONTAINER (notebook), 5);

  return notebook;
}


void         
nd_gui_trace_new_tab(LND_Trace *trace)
{
  ND_Trace  *trace_gui;
  GtkWidget *tab_content, *tab_label_content, *scrolledwin;
  GtkWidget *trace_list, *notebook, *label, *button, *pixmap;
  GtkTooltips *tt;
  const char *file;

  D_ENTER;

  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  tt = gtk_object_get_data(GTK_OBJECT(nd_toplevel_window), "tooltips");

  /* Build tab: */

  tab_content = gtk_vpaned_new ();
  gtk_widget_ref (tab_content);
  gtk_widget_show (tab_content);
  /* gtk_paned_set_position (GTK_PANED (tab_content), 200);*/

  scrolledwin = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_ref (scrolledwin);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "scrolledwin", scrolledwin,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (scrolledwin);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
  gtk_paned_pack1 (GTK_PANED (tab_content), scrolledwin, TRUE, TRUE);

  trace_list = gui_new_trace_list();
  gtk_widget_ref (trace_list);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "trace_list", trace_list,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_container_add (GTK_CONTAINER (scrolledwin), trace_list);
  /* gtk_paned_add1 (GTK_PANED (tab_content), trace_list); */

  notebook = gui_new_trace_proto_notebook();
  gtk_widget_ref (notebook);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "notebook", notebook,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show(notebook);
  gtk_paned_pack2 (GTK_PANED (tab_content), notebook, FALSE, TRUE);

  gtk_signal_connect (GTK_OBJECT (notebook), "switch_page",
                      GTK_SIGNAL_FUNC (on_protos_notebook_switch_page),
                      notebook);


  gtk_object_set_data(GTK_OBJECT(tab_content), "trace", trace);
  gtk_object_set_data(GTK_OBJECT(notebook), "trace", trace);

  /* Build tab label: */
  if (trace->filename)
    file = g_basename(trace->filename);
  else
    file = trace->unnamed;

  /* Put everything in a tab in an hbox: */

  tab_label_content = gtk_hbox_new (FALSE, 0);
  gtk_widget_ref (tab_label_content);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "tab_label_content",
			    tab_label_content, (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (tab_label_content);

  /* Okay -- I want to have a tooltip with the  full file name on this
     tab. Label's don't work with tooltips, so I have to wrap the
     label in an event box. But if I do that, the tabs don't get
     switched by clicking on the label, and worse, they get drawn
     differently. I'll comment this out for now.

  eb = gtk_event_box_new();
  gtk_widget_show(eb);
  gtk_box_pack_start (GTK_BOX (tab_label_content), eb, TRUE, TRUE, 0);

  tt = gtk_object_get_data(GTK_OBJECT(nd_toplevel_window), "tooltips");
  gtk_tooltips_set_tip (GTK_TOOLTIPS(tt), eb, trace->filename, NULL);
  */

  /* First thing in the box is the label containing the file name: */

  label = gtk_label_new (file);
  gtk_widget_ref (label);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "tab_label", label,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (label);
  gtk_container_add(GTK_CONTAINER(tab_label_content), label);
  gtk_misc_set_padding (GTK_MISC (label), 8, 0);
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);


  /* Then there's the button with the apply-to setting: */

  pixmap = create_pixmap (tab_label_content, "apply_to_sel.xpm");
  gtk_widget_ref (pixmap);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "pixmap_apply_sel", pixmap,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (pixmap);
  gtk_misc_set_padding (GTK_MISC (pixmap), 0, 0);
  gtk_pixmap_set_build_insensitive (GTK_PIXMAP (pixmap), FALSE);


  pixmap = create_pixmap (tab_label_content, "apply_to_part.xpm");
  gtk_widget_ref (pixmap);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "pixmap_apply_part", pixmap,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (pixmap);
  gtk_misc_set_padding (GTK_MISC (pixmap), 0, 0);
  gtk_pixmap_set_build_insensitive (GTK_PIXMAP (pixmap), FALSE);


  pixmap = create_pixmap (tab_label_content, "apply_to_all.xpm");
  gtk_widget_ref (pixmap);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "pixmap_apply_all", pixmap,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (pixmap);
  gtk_misc_set_padding (GTK_MISC (pixmap), 0, 0);
  gtk_pixmap_set_build_insensitive (GTK_PIXMAP (pixmap), FALSE);


  button = gtk_button_new();
  gtk_container_add (GTK_CONTAINER (button), pixmap);
  gtk_widget_ref (button);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "apply_button", button,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_signal_connect (GTK_OBJECT (button), "button_press_event",
		      GTK_SIGNAL_FUNC (on_trace_menu_press_event),
		      NULL);
  gtk_tooltips_set_tip(tt, button, _("Packet Iteration Mode"), NULL);
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (tab_label_content), button, FALSE, FALSE, 0);  

  
  /* And the close button, with two pixmaps depending on the trace's state: */

  pixmap = create_pixmap (tab_label_content, "delete.xpm");
  gtk_widget_ref (pixmap);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "pixmap_red", pixmap,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (pixmap);
  gtk_misc_set_padding (GTK_MISC (pixmap), 0, 0);
  gtk_pixmap_set_build_insensitive (GTK_PIXMAP (pixmap), FALSE);

  pixmap = create_pixmap (tab_label_content, "delete_okay.xpm");
  gtk_widget_ref (pixmap);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "pixmap_green", pixmap,
			    (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (pixmap);
  gtk_misc_set_padding (GTK_MISC (pixmap), 0, 0);
  gtk_pixmap_set_build_insensitive (GTK_PIXMAP (pixmap), FALSE);

  button = gtk_button_new();
  gtk_container_add (GTK_CONTAINER (button), pixmap);
  gtk_widget_ref (button);
  gtk_object_set_data_full (GTK_OBJECT (tab_content), "close_button", button,
			    (GtkDestroyNotify) gtk_widget_unref);
  
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      GTK_SIGNAL_FUNC (on_trace_close_clicked),
                      trace);
  gtk_tooltips_set_tip(tt, button, _("Close Trace"), NULL);
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (tab_label_content), button, FALSE, FALSE, 0);  

  gtk_paned_set_position(GTK_PANED(tab_content), nd_toplevel_window->allocation.height - 250);

  /* Hook up results: */

  trace_gui->tab = tab_content;
  trace_gui->tab_label = tab_label_content;
  trace_gui->list = trace_list;

  nd_gui_iterator_mode_set(trace);
  D_RETURN;
}


void         
nd_gui_trace_set_name(LND_Trace *trace)
{
  ND_Trace   *trace_gui;
  const char *sp;
  GtkLabel   *label;

  D_ENTER;

  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  ND_GTK_GET(label, trace_gui->tab, "tab_label");

  if (trace->filename)
    sp = g_basename(trace->filename);
  else
    sp = trace->unnamed;
  
  gtk_label_set_text(label, sp);
  D_RETURN;
}


void         
nd_gui_trace_add(LND_Trace *trace)
{
  ND_Trace  *trace_gui;
  GtkWidget *notebook;
  GtkWidget *w;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  ND_GTK_GET(notebook, nd_toplevel_window, "traces_notebook");
  ND_GTK_GET(w, nd_toplevel_window, "background_vbox");

  if (GTK_WIDGET_VISIBLE(w))
    {
      gtk_widget_hide(w);
      gtk_widget_show(notebook);
    }

  gtk_notebook_append_page(GTK_NOTEBOOK(notebook), trace_gui->tab, trace_gui->tab_label);

  /* Switch to the  new trace, calls nd_trace_registy_set_current()
     in the callback! */
  gtk_notebook_set_page(GTK_NOTEBOOK(notebook), 
			gtk_notebook_page_num(GTK_NOTEBOOK(notebook), trace_gui->tab));

  nd_gui_sync_edit_menu();
}


void         
nd_gui_trace_remove(LND_Trace *trace)
{
  ND_Trace   *trace_gui;
  GtkWidget  *notebook;
  gint        trace_num;

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  ND_GTK_GET(notebook, nd_toplevel_window, "traces_notebook");
  trace_num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), trace_gui->tab);
  gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), trace_num);

  /* We have a few special cases if we've closed all
   * remaining trace files. In that case, show the
   * background, and reset the window title.
   */
  if (gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)) < 0)
    {
      GtkWidget *w;

      ND_GTK_GET(w, nd_toplevel_window, "background_vbox");
      gtk_widget_show(w);

      gtk_widget_hide(notebook);

      gtk_window_set_title(GTK_WINDOW(nd_toplevel_window), "Netdude");
    }

  nd_gui_num_packets_set();
  nd_gui_sync_menus();
}


void    
nd_gui_widget_set_color(GtkWidget *widget, ND_GuiColor color)
{
  int i;
  GtkRcStyle *rc_style = gtk_rc_style_new();

  switch (color)
    {
    case ND_COLOR_RED:
      for (i=0; i<5; i++)
	{
	  rc_style->bg[i] = red[i];
	  rc_style->color_flags[i] = GTK_RC_BG;
	}
      break;

    case ND_COLOR_YELLOW:
      for (i=0; i<5; i++)
	{
	  rc_style->bg[i] = yellow[i];
	  rc_style->color_flags[i] = GTK_RC_BG;
	}
      break;

    default:
      for (i=0; i<5; i++)
	{
	  rc_style->bg[i] = bg[i];
	  rc_style->color_flags[i] = GTK_RC_BG;
	}
    }
    
  /* FIXME: this leaks the widget's current style but I don't
   * know how to fix it (do I? Shouldn't GTK take care of it?)
   */
  gtk_widget_modify_style(widget, rc_style);
}


static void 
gui_menu_item_activate(GtkMenuItem *menuitem,
		       gpointer user_data)
{
  LND_Trace        *trace;
  ND_Trace         *trace_gui;
  ND_MenuEntryCB    callback;
  int               value;

  return_if_no_current_trace(trace);

  if (! (trace_gui = nd_trace_get(trace)))
    return;

  callback = (ND_MenuEntryCB) gtk_object_get_data(GTK_OBJECT(menuitem), "callback");
  value = GPOINTER_TO_INT(user_data);

  D_ASSERT_PTR(callback);
  if (callback)
    callback(trace_gui->cur_packet,
	     nd_trace_get_current_proto_header(trace),
	     value);
}


static void
gui_add_menu_item(GtkWidget *menu, ND_MenuData *data)
{
  GtkWidget *item;

  item = gtk_menu_item_new_with_label (data->label);
  gtk_widget_ref (item);
  gtk_object_set_data_full (GTK_OBJECT (menu), data->label, item,
                            (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show (item);
  gtk_container_add (GTK_CONTAINER (menu), item);

  gtk_object_set_data(GTK_OBJECT(item), "callback", data->callback);

  gtk_tooltips_set_tip(tt, item, data->tooltip, NULL);

  gtk_signal_connect (GTK_OBJECT (item), "activate",
                      GTK_SIGNAL_FUNC (gui_menu_item_activate),
                      GINT_TO_POINTER((gint) data->value));
}



GtkWidget   *
nd_gui_create_menu(ND_MenuData *data)
{
  GtkWidget *menu;
  int        i;

  if (!data)
    return NULL;

  menu = gtk_menu_new ();

  for (i = 0; data[i].label; i++)
    gui_add_menu_item(menu, &data[i]);
  
  return menu;
}


/* Callback dispatcher -- receives callbacks on the Gtk level from
   the protocol's field table, retrieves necessary data and calls
   user's callback. */
static void
gui_proto_field_cb(GtkButton       *button,
		   gpointer         user_data)
{
  LND_Packet       *current;
  LND_Trace        *trace;
  LND_ProtoInfo    *pinf;
  LND_ProtoInst    *pi;
  ND_ProtoField    *field;
  void             *header;
  guint             offset;

  return_if_no_current_trace(trace);

  D_ENTER;

  field = (ND_ProtoField *) user_data;
  D_ASSERT_PTR(field);

  pi = nd_trace_get_current_proto_inst(trace);
  D_ASSERT_PTR(pi);

  pinf = nd_trace_get_proto_info(trace, pi->proto, pi->nesting);
  D_ASSERT_PTR(pinf);

  if (! (current = nd_trace_get_current_packet(trace)))
    {
      D(("No currently selected packet!? -- aborting\n"));
      return;
    }

  D_ASSERT((nd_trace_get_current_proto_header(trace) ==
	    libnd_packet_get_data(current, pi->proto, pi->nesting)),
	   "Field callback dispatcher uses wrong packet header!");

  header = nd_trace_get_current_proto_header(trace);
  offset = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "offset"));

  if (field->callback)
    {
      nd_gui_proto_table_block_events(trace, pinf);
      D(("Field %s callback on packet %p with offset %i\n",
	 field->label, current, offset));
      field->callback(current, header, ((guchar*) header) + offset);
      nd_gui_proto_table_unblock_events(trace, pinf);
    }
  
  D_RETURN;
  TOUCH(button);
}


/**
 * gui_table_create_button - creates a button widget for a header field
 * @parent: parent widget, for automatic destruction of new button
 * @pinf: the protocol info that provides nesting info and the hash to store the buttons
 * @field: the field for which the buttons is created
 * @label: override label if the one given in @field isn't wanted (used
 * for multi-line buttons)
 * @offset: offset of the new button, stored with the button widget as "offset"
 * @is_error: whether the button is in error state and thus painted red
 * @set_data: whether to store the button in the packet info hashtable
 */
static GtkWidget *
gui_table_create_button(GtkWidget *parent,
			LND_ProtoInfo *pinf,
			ND_ProtoField *field,
			const char *label,
			guint offset,
			gboolean is_error, gboolean set_data)
{
  GtkWidget *button = NULL;
  const char *used_label = field->label;
  
  if (label)
    used_label = label;
  
  switch (field->type)
    {
    case ND_FLG_FIELD:
      button = gtk_toggle_button_new_with_label(used_label);
      break;

    case ND_VAL_FIELD:
    default:
      button = gtk_button_new_with_label(used_label);
    }

  gtk_tooltips_set_tip (GTK_TOOLTIPS(tt), button, field->tooltip, NULL);
  
  /* Arrange for automatic cleanup of the button: */
  gtk_widget_ref(button);
  gtk_widget_show(button);
  gtk_object_set_data_full(GTK_OBJECT (parent), used_label,
			   button, (GtkDestroyNotify) gtk_widget_unref);

  /* Store the fields offset with the button */
  gtk_object_set_data(GTK_OBJECT(button), "offset", GINT_TO_POINTER((offset >> 3)));
      
  /* If wanted, hook up the button in the packet info hash table */
  if (set_data)
    libnd_reg_set_data(pinf->registry, nd_proto_field_to_string(field), button);
  
  /* If the field provides a callback, hook it in, otherwise set
   * the button insensitive so that it can't be pressed */
  if (field->callback)
    {
      gtk_signal_connect(GTK_OBJECT (button), "clicked",
			 GTK_SIGNAL_FUNC (gui_proto_field_cb),
			 field);
    }
  else
    {
      gtk_widget_set_sensitive(button, FALSE);
    }
  
  /* If something's wrong with it, paint it. */
  nd_gui_widget_set_color(button, (is_error ? ND_COLOR_RED : ND_COLOR_BG));

  return button;
}


GtkWidget *
nd_gui_proto_table_create(LND_Trace *trace,
			  LND_ProtoInfo *pinf)
{
  int           i, x, y, bpl, w_top, w_middle, w_bot, h_middle, largest = 0;
  GtkWidget    *proto_table, *button;
  char         *label;
  guint         offset = 0;
  LND_Protocol *proto;
  ND_Protocol  *proto_gui;

  if (!trace || !pinf)
    return NULL;

  proto = pinf->inst.proto;
  proto_gui = nd_proto_get(proto);
  D_ASSERT_PTR(proto_gui->fields);
  bpl = proto_gui->header_width;

  /* Create the GUI tab for this protocol: ---------------------------- */

  /* Create the table itself: */

  proto_table = gtk_table_new (proto_gui->header_size / bpl, bpl, TRUE);
  gtk_widget_show (proto_table);
  gtk_container_set_border_width (GTK_CONTAINER (proto_table), 5);
  
/*   This is experimental stuff that could become a bit # indicator */
/*   at some point. Right now it sucks, so it's commented out.  */
/*
  {
    char s[MAXPATHLEN];
    GtkWidget *bitnum;
    for (i = 0; i < proto_gui->header_width; i += 4)
      {
	g_snprintf(s, MAXPATHLEN, "%i", i);
	bitnum = gtk_label_new(s);
	gtk_widget_show(bitnum);
	
	gtk_table_attach (GTK_TABLE (proto_table), bitnum,
			  i, i + 1,
			  0, 1,
			  (GtkAttachOptions) 0,
			  (GtkAttachOptions) 0, 0, 0);	
      }
  }
*/

  /* Iterate over all fixed header fields and hook buttons into
   * the table: */
  for (i = x = 0, y = 0; proto_gui->fields[i].label; i++)
    {
      w_middle = h_middle = w_bot = 0;      
      w_top = proto_gui->fields[i].bits;
      
      if (x + w_top > bpl)
	w_top = bpl - x;

      if (w_top < proto_gui->fields[i].bits)
	{
	  w_middle = proto_gui->fields[i].bits - w_top;
	  h_middle = MAX(w_middle / bpl, 1);

	  if (w_middle > bpl)
	    {
	      w_middle = bpl;	      
	      w_bot = proto_gui->fields[i].bits - w_top - h_middle * bpl;
	    }

	  largest = (w_middle > w_top ? 1 : 0);
	}

      
      label = (largest == 0 ? NULL : _("->"));
      button = gui_table_create_button(proto_table, pinf, &proto_gui->fields[i],
				       label, offset, FALSE, TRUE);

      gtk_table_attach (GTK_TABLE (proto_table), button,
			x, x + w_top,
			y, y + 1,
			(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
			(GtkAttachOptions) 0, 0, 0);

      x += w_top;

      if (w_middle)
	{
	  int options;

	  x = 0;
	  y++;
	  label = (largest == 1 ? NULL : _("<-"));
	  options = (h_middle > 1 ? (GTK_EXPAND | GTK_FILL) : 0);

	  button = gui_table_create_button(proto_table, pinf, &proto_gui->fields[i],
					   label, offset, FALSE, TRUE);
	  gtk_table_attach (GTK_TABLE (proto_table), button,
			    0, w_middle,
			    y, y + h_middle,
			    (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
			    (GtkAttachOptions) options, 0, 0);

	  x = w_middle;
	  y += h_middle;
	}

      if (w_bot)
	{
	  button = gui_table_create_button(proto_table, pinf, &proto_gui->fields[i],
					   _("<-"), offset, FALSE, FALSE);
	  gtk_table_attach (GTK_TABLE (proto_table), button,
			    0, w_bot,
			    y, y + 1,
			    (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
			    (GtkAttachOptions) 0, 0, 0);

	  x = w_bot;
	}
      
      if (x >= bpl)
	{
	  y++;
	  x = 0;
	}

      offset += proto_gui->fields[i].bits;
    }

  return proto_table;
}


void
nd_gui_proto_table_add(LND_Trace *trace,
		       LND_ProtoInfo *pinf,
		       ND_ProtoField *field,
		       void *data,
		       gboolean is_error)
{
  GList         *opt_widgets;
  GtkWidget     *table, *button;
  GtkTooltips   *tt;
  int            w_top, w_middle, w_bot, h_middle, largest = 0;
  int            x, y, offset;
  char           label[MAXPATHLEN], *final_label;
  LND_Protocol  *proto;
  ND_Protocol   *proto_gui;
  ND_ProtoInfo  *pinf_gui;

  if (!trace || !pinf)
    return;

  proto = pinf->inst.proto;
  pinf_gui  = nd_proto_info_get(pinf);
  proto_gui = nd_proto_get(proto);

  opt_widgets = libnd_reg_get_data(pinf->registry, nd_proto_get_opt_key(proto));

  table = pinf_gui->proto_gui;
  D_ASSERT_PTR(table);
  ND_GTK_GET(tt, nd_toplevel_window, "tooltips");

  x = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(table), "options_x"));
  y = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(table), "options_y"));
  offset = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(table), "options_offset"));

  g_snprintf(label, MAXPATHLEN, field->label, data);
  w_middle = h_middle = w_bot = 0;      
  w_top = field->bits;
      
  if (x + w_top > proto_gui->header_width)
    w_top = proto_gui->header_width - x;

  if (w_top < field->bits)
    {
      w_middle = field->bits - w_top;
      h_middle = MAX(w_middle / proto_gui->header_width, 1);

      if (w_middle > proto_gui->header_width)
	{
	  w_middle = proto_gui->header_width;
	  w_bot = field->bits - w_top - h_middle * proto_gui->header_width;
	}

      largest = (w_middle > w_top ? 1 : 0);
    }

  final_label = (largest == 0 ? label : _("->"));

  button = gui_table_create_button(table, pinf, field,
				   final_label, offset,
				   is_error, TRUE);
  opt_widgets = g_list_append(opt_widgets, button);

  gtk_table_attach(GTK_TABLE(table), button,
		   x, x + w_top,
		   y, y + 1,
		   (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
		   (GtkAttachOptions) 0, 0, 0);

  x += w_top;

  if (w_middle)
    {
      int options;

      y++;
      final_label = (largest == 1 ? label : _("<-"));
      options = (h_middle > 1 ? (GTK_EXPAND | GTK_FILL) : 0);

      button = gui_table_create_button(table, pinf, field,
				       final_label, offset,
				       FALSE, TRUE);
      opt_widgets = g_list_append(opt_widgets, button);
      gtk_table_attach (GTK_TABLE (table), button,
			0, w_middle,
			y, y + h_middle,
			(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
			(GtkAttachOptions) options, 0, 0);

      x = w_middle;
      y += h_middle;
    }

  if (w_bot)
    {
      button = gui_table_create_button(table, pinf, field,
				       _("<-"), offset,
				       FALSE, FALSE);
      opt_widgets = g_list_append(opt_widgets, button);
      gtk_table_attach (GTK_TABLE (table), button,
			0, w_bot,
			y, y + 1,
			(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
			(GtkAttachOptions) 0, 0, 0);

      x = w_bot;
    }

  if (x == proto_gui->header_width)
    {
      x = 0;
      y++;
    }

  gtk_object_set_data(GTK_OBJECT(table), "options_x", GINT_TO_POINTER(x));
  gtk_object_set_data(GTK_OBJECT(table), "options_y", GINT_TO_POINTER(y));
  gtk_object_set_data(GTK_OBJECT(table), "options_offset", GINT_TO_POINTER(offset + field->bits));

  libnd_reg_set_data(pinf->registry, nd_proto_get_opt_key(proto), opt_widgets);
}


void    
nd_gui_proto_table_clear(LND_Trace *trace, LND_ProtoInfo *pinf)
{
  LND_Protocol *proto;
  ND_Protocol  *proto_gui;
  ND_ProtoInfo *pinf_gui;
  GList        *opt_widgets;
  GList        *l;
  GtkWidget    *table;
  int           x, y;

  if (!trace || !pinf)
    return;

  proto = pinf->inst.proto;
  pinf_gui = nd_proto_info_get(pinf);
  proto_gui = nd_proto_get(proto);

  table = pinf_gui->proto_gui;
  D_ASSERT_PTR(table);

  x = proto_gui->header_size % proto_gui->header_width;
  y = proto_gui->header_size / proto_gui->header_width;

  gtk_object_set_data(GTK_OBJECT(table), "options_x", GINT_TO_POINTER(x));
  gtk_object_set_data(GTK_OBJECT(table), "options_y", GINT_TO_POINTER(y));
  gtk_object_set_data(GTK_OBJECT(table), "options_offset", GINT_TO_POINTER(proto_gui->header_size));

  if ( (opt_widgets = libnd_reg_get_data(pinf->registry, nd_proto_get_opt_key(proto))))
    {
      for (l = g_list_first(opt_widgets); l; l = g_list_next(l))
	{
	  gtk_container_remove(GTK_CONTAINER(table), GTK_WIDGET(l->data));
	  gtk_widget_destroy(GTK_WIDGET(l->data));
	}
      
      g_list_free(opt_widgets);
      libnd_reg_del_data(pinf->registry, nd_proto_get_opt_key(proto));
    }
}


void          
nd_gui_proto_table_block_events(LND_Trace * trace, LND_ProtoInfo *pinf)
{
  LND_Protocol *proto;
  ND_Protocol  *proto_gui;
  GtkWidget    *button;
  int           i;

  if (!trace || !pinf)
    return;

  proto = pinf->inst.proto;
  proto_gui = nd_proto_get(proto);

  if (!proto_gui->fields)
    return;

  for (i = 0; proto_gui->fields[i].label; i++)
    {
      if (proto_gui->fields[i].callback)
	{
	  button = libnd_reg_get_data(pinf->registry, nd_proto_field_to_string(&proto_gui->fields[i]));
	  D_ASSERT_PTR(button);
	  
	  gtk_signal_handler_block_by_func(GTK_OBJECT(button),
					   gui_proto_field_cb, &(proto_gui->fields[i]));
	}
    }
}


void          
nd_gui_proto_table_unblock_events(LND_Trace * trace, LND_ProtoInfo *pinf)
{
  LND_Protocol *proto;
  ND_Protocol  *proto_gui;
  GtkWidget    *button;
  int           i;

  if (!trace || !pinf)
    return;

  proto = pinf->inst.proto;
  proto_gui = nd_proto_get(proto);

  if (!proto_gui->fields)
    return;

  for (i = 0; proto_gui->fields[i].label; i++)
    {
      if (proto_gui->fields[i].callback)
	{
	  button = libnd_reg_get_data(pinf->registry, nd_proto_field_to_string(&proto_gui->fields[i]));
	  D_ASSERT_PTR(button);
	  
	  gtk_signal_handler_unblock_by_func(GTK_OBJECT(button),
					     gui_proto_field_cb, &(proto_gui->fields[i]));
	}
    }
}


void      
nd_gui_proto_menu_register(LND_Protocol *proto)
{
  ND_Protocol *proto_gui;
  GtkWidget   *proto_menu;
  GtkWidget   *proto_menu_item;
  GList       *child;
  int          child_index;
  char        *label;

  if (!proto)
    return;

  proto_gui = nd_proto_get(proto);
  
  if (!proto_gui->menu)
    return;

  /* If the menu item already exists, we've been here before. */
  if (proto_gui->proto_menu_item)
    return;

  D(("Hooking in menu for protocol %s\n", proto->name));

  ND_GTK_GET(proto_menu, nd_toplevel_window, "proto_menu");
  
  for (child_index = 0, child = GTK_MENU_SHELL(proto_menu)->children; child;
       child = g_list_next(child), child_index++)
    {
      if (! GTK_BIN(child->data)->child)
	break;

      gtk_label_get(GTK_LABEL(GTK_BIN(child->data)->child), &label);
      
      if (strcmp(proto->name, label) < 0)
	break;
    }

  proto_menu_item = gtk_menu_item_new_with_label (proto->name);
  proto_gui->proto_menu_item = proto_menu_item;

  gtk_widget_ref (proto_menu_item);
  gtk_object_set_data_full (GTK_OBJECT (nd_toplevel_window), proto->name, proto_menu_item,
                            (GtkDestroyNotify) gtk_widget_unref);
  gtk_widget_show(proto_menu_item);
  gtk_menu_insert(GTK_MENU(proto_menu), proto_menu_item, child_index);
  gtk_widget_show(proto_gui->menu);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (proto_menu_item), proto_gui->menu);
}


void          
nd_gui_add_monowidth_widget(GtkWidget *widget)
{
  char         *fontname;
  GtkStyle     *style;

  if (!widget)
    return;

  if (!libnd_prefs_get_str_item(ND_DOM_NETDUDE, "font_mono", &fontname))
    fontname = "-*-courier-medium-r-normal-*-*-100-*-*-m-*-*";

  style = gtk_style_copy(gtk_widget_get_style(widget));
  gdk_font_unref(style->font);
  style->font = gdk_font_load (fontname);
  gtk_widget_set_style(widget, style);

  if (GTK_IS_CLIST(widget))
    {
      gtk_clist_set_row_height(GTK_CLIST(widget),
      			       widget->style->font->ascent +
			       widget->style->font->descent + 2);

      /* This is a workaround for what appears to be a bug in
	 the clist code -- the vertical font alignment isn't
	 set correctly sometimes: */
      GTK_CLIST(widget)->row_center_offset = widget->style->font->ascent + 1.5;
    }

  gtk_widget_queue_draw(widget);
  while (gtk_events_pending())
    gtk_main_iteration();

  if (g_list_index(monowidth_widgets, widget) < 0)
    monowidth_widgets = g_list_prepend(monowidth_widgets, widget);
}


void
nd_gui_del_monowidth_widget(GtkWidget *widget)
{
  GList *l;

  if (!widget)
    return;

  if ( (l = g_list_find(monowidth_widgets, widget)))
    monowidth_widgets = g_list_remove_link(monowidth_widgets, l);
}


void          
nd_gui_update_monowidth_widgets(void)
{
  GList *l;

  for (l = monowidth_widgets; l; l = g_list_next(l))
    nd_gui_add_monowidth_widget(l->data);
}



void
nd_gui_update_view_indicator(void)
{
  LND_Trace *trace;
  ND_Trace *trace_gui;
  GtkWidget *view_button, *area;
  double size, fraction;
  char buf[MAXPATHLEN];
  off_t offset;

  D_ENTER;

  ND_GTK_GET(area, nd_toplevel_window, "view_fixed");
  ND_GTK_GET(view_button, nd_toplevel_window, "view_fixed_button");
  
  if (! (trace = nd_trace_registry_get_current()))
    {
      gtk_widget_hide(view_button);
      D_RETURN;
    }

  switch (trace->area.mode)
    {
    case LND_TPM_TIME:
      fraction = libnd_tpm_get_time_fraction(trace->tpm, &trace->tpm->current->start_ts);
      break;
      
    default:
      offset = libnd_tpm_map_loc_to_offset(trace->tpm, &trace->tpm->current->start);
      D(("Offset: %lu\n", (long unsigned) offset));
      fraction = libnd_tpm_get_space_fraction(trace->tpm, offset);
    }

  size = libnd_tpm_get_rel_size(trace->tpm);
  D(("Trace part stats: size = %f, offset = %f\n", size, fraction));

  /* Position and width of the button -- make sure that the button is
   * at least 10 pixels wide, and that does fit into the right edge
   */
  gtk_widget_set_uposition (view_button, MIN(fraction * area->allocation.width, area->allocation.width - 10), 0);
  gtk_widget_set_usize (view_button, MAX(size * area->allocation.width, 10),
			area->allocation.height / 2);
  gtk_widget_show(view_button);  

  /* Also update title column in the list widget: */
  if (! (trace_gui = nd_trace_get(trace)))
    D_RETURN;

  /* If we're at the very beginning, and take all the space,
   * don't show percentage details.
   */
  if (fraction < 0.001 && size > 0.999)
    {
      g_snprintf(buf, MAXPATHLEN, _("%s"), list_titles[0]);
    }
  else
    {
      g_snprintf(buf, MAXPATHLEN, _("%s (%.2f%% of file at %.2f%% offset)"),
		 list_titles[0], 100 * size, 100 * fraction);
    }

  gtk_clist_set_column_title(GTK_CLIST(trace_gui->list), 0, buf);
  D_RETURN;
}


void
nd_gui_update_area_indicator(void)
{
  LND_Trace *trace;
  GtkWidget *area_button, *area;
  double size, start;

  D_ENTER;

  ND_GTK_GET(area, nd_toplevel_window, "view_fixed");
  ND_GTK_GET(area_button, nd_toplevel_window, "view_fixed_area_button");
  
  if (! (trace = nd_trace_registry_get_current()))
    {
      gtk_widget_hide(area_button);
      D_RETURN;
    }

  switch (trace->area.mode)
    {
    case LND_TPM_TIME:
      start = libnd_tpm_get_time_fraction(trace->tpm, &trace->area.area_time_start);
      size = libnd_tpm_get_time_fraction(trace->tpm, &trace->area.area_time_end) - start;
      break;

    default:
      start = trace->area.area_space_start;
      size = trace->area.area_space_end - trace->area.area_space_start;
    }
 
  /* Position and width of the button -- make sure that the button is
   * at least 10 pixels wide, and that does fit into the right edge
   */
  gtk_widget_set_uposition (area_button, MIN(start * area->allocation.width, area->allocation.width - 10),
			    area->allocation.height / 2);
  gtk_widget_set_usize (area_button, MAX(size * area->allocation.width, 10),
			area->allocation.height / 2);
  gtk_widget_show(area_button);  

  nd_gui_update_view_indicator();

  D_RETURN;
}


syntax highlighted by Code2HTML, v. 0.9.1