Added "Capture" and "Display" menus; "Capture" has a "Start" item, which
[obnox/wireshark/wip.git] / column.c
1 /* column.c
2  * Routines for handling column preferences
3  *
4  * $Id: column.c,v 1.11 1999/06/19 01:14:49 guy Exp $
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@zing.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * 
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  * 
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33
34 #include <gtk/gtk.h>
35
36 #include <stdio.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <unistd.h>
40 #include <sys/stat.h>
41
42 #include "timestamp.h"
43 #include "prefs.h"
44 #include "column.h"
45 #include "packet.h"
46
47 extern e_prefs prefs;
48
49 static GtkWidget *column_l, *chg_bt, *del_bt, *title_te, *fmt_m, *up_bt,
50                  *dn_bt;
51 static gint       cur_fmt;
52
53 #define E_COL_NAME_KEY "column_name"
54 #define E_COL_LBL_KEY  "column_label"
55 #define E_COL_CM_KEY   "in_col_cancel_mode"
56
57 static gchar *col_format_to_string(gint);
58 static gchar *col_format_desc(gint);
59 static gint   get_column_format_from_str(gchar *str);
60 static void   column_sel_list_cb(GtkWidget *, gpointer);
61 static void   column_sel_new_cb(GtkWidget *, gpointer);
62 static void   column_sel_chg_cb(GtkWidget *, gpointer);
63 static void   column_sel_del_cb(GtkWidget *, gpointer);
64 static void   column_sel_arrow_cb(GtkWidget *, gpointer);
65 static void   column_set_fmt_cb(GtkWidget *, gpointer);
66
67 /* Given a format number (as defined in ethereal.h), returns its equivalent
68    string */
69 static gchar *
70 col_format_to_string(gint fmt) {
71   gchar *slist[] = { "%m", "%t", "%Rt", "%At", "%Tt", "%s", "%rs", "%us",
72                      "%hs", "%rhs", "%uhs", "%ns", "%rns", "%uns", "%d",
73                      "%rd", "%ud", "%hd", "%rhd", "%uhd", "%nd", "%rnd",
74                      "%und", "%S", "%rS", "%uS", "%D", "%rD", "%uD", "%p",
75                      "%i" };
76   
77   if (fmt < 0 || fmt > NUM_COL_FMTS)
78     return NULL;
79   
80   return(slist[fmt]);
81 }
82
83 /* Given a format number (as defined in ethereal.h), returns its
84   description */
85 static gchar *
86 col_format_desc(gint fmt) {
87   gchar *dlist[] = { "Number", "Time (command line specified)",
88                      "Relative time", "Absolute time", "Delta time",
89                      "Source address", "Src addr (resolved)",
90                      "Src addr (unresolved)", "Hardware src addr",
91                      "Hw src addr (resolved)", "Hw src addr (unresolved)",
92                      "Network src addr", "Net scr addr (resolved)",
93                      "Net src addr (unresolved)", "Destination address",
94                      "Dest addr (resolved)", "Dest addr (unresolved)",
95                      "Hardware dest addr", "Hw dest addr (resolved)",
96                      "Hw dest addr (unresolved)", "Network dest addr",
97                      "Net dest addr (resolved)", "Net dest addr (unresolved)",
98                      "Source port", "Src port (resolved)",
99                      "Src port (unresolved)", "Destination port",
100                      "Dest port (resolved)", "Dest port (unresolved)",
101                      "Protocol", "Information" };
102   
103   if (fmt < 0 || fmt > NUM_COL_FMTS)
104     return NULL;
105   
106   return(dlist[fmt]);
107 }
108
109 /* Marks each array element true if it can be substituted for the given
110    column format */
111 void
112 get_column_format_matches(gboolean *fmt_list, gint format) {
113   int i;
114   
115   for (i = 0; i < NUM_COL_FMTS; i++) {
116     /* Get the obvious: the format itself */
117     if (i == format)
118       fmt_list[i] = TRUE;
119     /* Get any formats lower down on the chain */
120     switch (format) {
121       case COL_DEF_SRC:
122         fmt_list[COL_RES_DL_SRC] = TRUE;
123         fmt_list[COL_RES_NET_SRC] = TRUE;
124         break;
125       case COL_RES_SRC:
126         fmt_list[COL_RES_DL_SRC] = TRUE;
127         fmt_list[COL_RES_NET_SRC] = TRUE;
128         break;
129       case COL_UNRES_SRC:
130         fmt_list[COL_UNRES_DL_SRC] = TRUE;
131         fmt_list[COL_UNRES_NET_SRC] = TRUE;
132         break;
133       case COL_DEF_DST:
134         fmt_list[COL_RES_DL_DST] = TRUE;
135         fmt_list[COL_RES_NET_DST] = TRUE;
136         break;
137       case COL_RES_DST:
138         fmt_list[COL_RES_DL_DST] = TRUE;
139         fmt_list[COL_RES_NET_DST] = TRUE;
140         break;
141       case COL_UNRES_DST:
142         fmt_list[COL_UNRES_DL_DST] = TRUE;
143         fmt_list[COL_UNRES_NET_DST] = TRUE;
144         break;
145       case COL_DEF_DL_SRC:
146         fmt_list[COL_RES_DL_SRC] = TRUE;
147         break;
148       case COL_DEF_DL_DST:
149         fmt_list[COL_RES_DL_DST] = TRUE;
150         break;
151       case COL_DEF_NET_SRC:
152         fmt_list[COL_RES_NET_SRC] = TRUE;
153         break;
154       case COL_DEF_NET_DST:
155         fmt_list[COL_RES_NET_DST] = TRUE;
156         break;
157       case COL_DEF_SRC_PORT:
158         fmt_list[COL_RES_SRC_PORT] = TRUE;
159         break;
160       case COL_DEF_DST_PORT:
161         fmt_list[COL_RES_DST_PORT] = TRUE;
162         break;
163       default:
164         break;
165     }
166   }
167 }
168
169 /* Returns the longest possible width for a particular column type */
170 /* XXX - this is somewhat fragile; we should probably generate */
171 /* the summary lines for all the packets first, and compute the */
172 /* maximum column width as the maximum string width of all the */
173 /* values in that column. */
174 gint
175 get_column_width(gint format, GdkFont *font) {
176   switch (format) {
177     case COL_NUMBER:
178       return (gdk_string_width(font, "0") * 7);
179       break;
180     case COL_CLS_TIME:
181       if (timestamp_type == COL_ABS_TIME)
182         return (gdk_string_width(font, "00:00:00.000000"));
183       else
184         return (gdk_string_width(font, "0000.000000"));
185       break;
186     case COL_ABS_TIME:
187       return (gdk_string_width(font, "00:00:00.000000"));
188       break;
189     case COL_REL_TIME:
190     case COL_DELTA_TIME:
191       return (gdk_string_width(font, "0000.000000"));
192       break;
193     case COL_DEF_SRC:
194     case COL_RES_SRC:
195     case COL_UNRES_SRC:
196     case COL_DEF_DL_SRC:
197     case COL_RES_DL_SRC:
198     case COL_UNRES_DL_SRC:
199     case COL_DEF_NET_SRC:
200     case COL_RES_NET_SRC:
201     case COL_UNRES_NET_SRC:
202     case COL_DEF_DST:
203     case COL_RES_DST:
204     case COL_UNRES_DST:
205     case COL_DEF_DL_DST:
206     case COL_RES_DL_DST:
207     case COL_UNRES_DL_DST:
208     case COL_DEF_NET_DST:
209     case COL_RES_NET_DST:
210     case COL_UNRES_NET_DST:
211       return (gdk_string_width(font, "00000000.000000000000")); /* IPX-style */
212       break;
213     case COL_DEF_SRC_PORT:
214     case COL_RES_SRC_PORT:
215     case COL_UNRES_SRC_PORT:
216     case COL_DEF_DST_PORT:
217     case COL_RES_DST_PORT:
218     case COL_UNRES_DST_PORT:
219       return (gdk_string_width(font, "0") * 6);
220       break;
221     case COL_PROTOCOL:
222       return (gdk_string_width(font, "NBNS (UDP)"));
223       break;
224     default: /* COL_INFO */
225       return (gdk_string_width(font, "Source port: kerberos-master  "
226         "Destination port: kerberos-master"));
227       break;
228   }
229 }
230
231 #define TIME_DEF 0
232 #define TIME_REL 1
233 #define TIME_ABS 2
234 #define TIME_DEL 3
235
236 #define RES_DEF  0
237 #define RES_DO   1
238 #define RES_DONT 2
239
240 #define ADDR_DEF 0
241 #define ADDR_DL  3
242 #define ADDR_NET 6
243
244 gint
245 get_column_format(gint col) {
246   GList    *clp = g_list_nth(prefs.col_list, col);
247   fmt_data *cfmt;
248   
249   cfmt = (fmt_data *) clp->data;
250   
251   return(get_column_format_from_str(cfmt->fmt));
252 }
253
254 static gint
255 get_column_format_from_str(gchar *str) {
256   gchar *cptr = str;
257   gint      res_off = RES_DEF, addr_off = ADDR_DEF, time_off = TIME_DEF;
258
259   /* To do: Make this parse %-formatted strings "for real" */
260   while (*cptr != '\0') {
261     switch (*cptr) {
262       case 't':  /* To do: fix for absolute and delta */
263         return COL_CLS_TIME + time_off;
264         break;
265       case 'm':
266         return COL_NUMBER;
267         break;
268       case 's':
269         return COL_DEF_SRC + res_off + addr_off;
270         break;
271       case 'd':
272         return COL_DEF_DST + res_off + addr_off;
273         break;
274       case 'S':
275         return COL_DEF_SRC_PORT + res_off;
276         break;
277       case 'D':
278         return COL_DEF_DST_PORT + res_off;
279         break;
280       case 'p':
281         return COL_PROTOCOL;
282         break;
283       case 'i':
284         return COL_INFO;
285         break;
286       case 'r':
287         res_off = RES_DO;
288         break;
289       case 'u':
290         res_off = RES_DONT;
291         break;
292       case 'h':
293         addr_off = ADDR_DL;
294         break;
295       case 'n':
296         addr_off = ADDR_NET;
297         break;
298       case 'R':
299         time_off = TIME_REL;
300         break;
301       case 'A':
302         time_off = TIME_ABS;
303         break;
304       case 'T':
305         time_off = TIME_DEL;
306         break;
307     }
308     cptr++;
309   }
310   return COL_NUMBER;
311 }
312
313 gchar *
314 get_column_title(gint col) {
315   GList    *clp = g_list_nth(prefs.col_list, col);
316   fmt_data *cfmt;
317   
318   cfmt = (fmt_data *) clp->data;
319
320   return(cfmt->title);  
321 }
322
323 #define MAX_FMT_PREF_LEN      1024
324 #define MAX_FMT_PREF_LINE_LEN   60
325 gchar *
326 col_format_to_pref_str() {
327   static gchar  pref_str[MAX_FMT_PREF_LEN] = "";
328   GList        *clp = g_list_first(prefs.col_list);
329   fmt_data     *cfmt;
330   int           cur_pos = 0, cur_len = 0, fmt_len;
331   
332   while (clp) {
333     cfmt = (fmt_data *) clp->data;
334     
335     fmt_len = strlen(cfmt->title) + 4;
336     if ((fmt_len + cur_len) < (MAX_FMT_PREF_LEN - 1)) {
337       if ((fmt_len + cur_pos) > MAX_FMT_PREF_LINE_LEN) {
338         cur_len--;
339         cur_pos = 0;
340                 pref_str[cur_len] = '\n'; cur_len++;
341         pref_str[cur_len] = '\t'; cur_len++;
342       }
343       sprintf(&pref_str[cur_len], "\"%s\", ", cfmt->title);
344       cur_len += fmt_len;
345       cur_pos += fmt_len;
346     }
347
348     fmt_len = strlen(cfmt->fmt) + 4;
349     if ((fmt_len + cur_len) < (MAX_FMT_PREF_LEN - 1)) {
350       if ((fmt_len + cur_pos) > MAX_FMT_PREF_LINE_LEN) {
351         cur_len--;
352         cur_pos = 0;
353         pref_str[cur_len] = '\n'; cur_len++;
354         pref_str[cur_len] = '\t'; cur_len++;
355       }
356       sprintf(&pref_str[cur_len], "\"%s\", ", cfmt->fmt);
357       cur_len += fmt_len;
358       cur_pos += fmt_len;
359     }
360     
361     clp = clp->next;
362   }
363   
364   if (cur_len > 2)
365     pref_str[cur_len - 2] = '\0';
366
367   return(pref_str);
368 }    
369
370 /* Create and display the column selection widgets. */
371 /* Called when the 'Columns' preference notebook page is selected. */
372 GtkWidget *
373 column_prefs_show() {
374   GtkWidget   *main_vb, *top_hb, *list_bb, *new_bt, *column_sc, *nl_item,
375               *nl_lb, *tb, *lb, *menu, *mitem, *arrow_hb;
376   GList       *clp = NULL;
377   fmt_data    *cfmt;
378   gint         i;
379
380   /* Container for each row of widgets */
381   main_vb = gtk_vbox_new(FALSE, 5);
382   gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
383   gtk_widget_show(main_vb);
384   gtk_object_set_data(GTK_OBJECT(main_vb), E_COL_CM_KEY, (gpointer)FALSE);
385   
386   /* Top row: Column list and buttons */
387   top_hb = gtk_hbox_new(FALSE, 5);
388   gtk_container_add(GTK_CONTAINER(main_vb), top_hb);
389   gtk_widget_show(top_hb);
390   
391   list_bb = gtk_vbutton_box_new();
392   gtk_button_box_set_layout (GTK_BUTTON_BOX (list_bb), GTK_BUTTONBOX_START);
393   gtk_container_add(GTK_CONTAINER(top_hb), list_bb);
394   gtk_widget_show(list_bb);
395
396   new_bt = gtk_button_new_with_label ("New");
397   gtk_signal_connect(GTK_OBJECT(new_bt), "clicked",
398     GTK_SIGNAL_FUNC(column_sel_new_cb), NULL);
399   gtk_container_add(GTK_CONTAINER(list_bb), new_bt);
400   gtk_widget_show(new_bt);
401   
402   chg_bt = gtk_button_new_with_label ("Change");
403   gtk_widget_set_sensitive(chg_bt, FALSE);
404   gtk_signal_connect(GTK_OBJECT(chg_bt), "clicked",
405     GTK_SIGNAL_FUNC(column_sel_chg_cb), NULL);
406   gtk_container_add(GTK_CONTAINER(list_bb), chg_bt);
407   gtk_widget_show(chg_bt);
408   
409   del_bt = gtk_button_new_with_label ("Delete");
410   gtk_widget_set_sensitive(del_bt, FALSE);
411   gtk_signal_connect(GTK_OBJECT(del_bt), "clicked",
412     GTK_SIGNAL_FUNC(column_sel_del_cb), NULL);
413   gtk_container_add(GTK_CONTAINER(list_bb), del_bt);
414   gtk_widget_show(del_bt);
415   
416   arrow_hb = gtk_hbox_new(TRUE, 3);
417   gtk_container_add(GTK_CONTAINER(list_bb), arrow_hb);
418   gtk_widget_show(arrow_hb);
419   
420   up_bt = gtk_button_new_with_label("Up");
421   gtk_widget_set_sensitive(up_bt, FALSE);
422   gtk_signal_connect(GTK_OBJECT(up_bt), "clicked",
423     GTK_SIGNAL_FUNC(column_sel_arrow_cb), NULL);
424   gtk_box_pack_start(GTK_BOX(arrow_hb), up_bt, TRUE, TRUE, 0);
425   gtk_widget_show(up_bt);
426   
427   dn_bt = gtk_button_new_with_label("Down");
428   gtk_widget_set_sensitive(dn_bt, FALSE);
429   gtk_signal_connect(GTK_OBJECT(dn_bt), "clicked",
430     GTK_SIGNAL_FUNC(column_sel_arrow_cb), NULL);
431   gtk_box_pack_start(GTK_BOX(arrow_hb), dn_bt, TRUE, TRUE, 0);
432   gtk_widget_show(dn_bt);
433   
434   column_sc = gtk_scrolled_window_new(NULL, NULL);
435   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(column_sc),
436     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
437   gtk_widget_set_usize(column_sc, 250, 150);
438   gtk_container_add(GTK_CONTAINER(top_hb), column_sc);
439   gtk_widget_show(column_sc);
440
441   column_l = gtk_list_new();
442   gtk_list_set_selection_mode(GTK_LIST(column_l), GTK_SELECTION_SINGLE);
443   gtk_signal_connect(GTK_OBJECT(column_l), "selection_changed",
444     GTK_SIGNAL_FUNC(column_sel_list_cb), main_vb);
445 #ifdef GTK_HAVE_FEATURES_1_1_4
446   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(column_sc), column_l);
447 #else
448   gtk_container_add(GTK_CONTAINER(column_sc), column_l);
449 #endif
450   gtk_widget_show(column_l);
451
452   clp = g_list_first(prefs.col_list);
453   while (clp) {
454     cfmt    = (fmt_data *) clp->data;
455     nl_lb   = gtk_label_new(cfmt->title);
456     nl_item = gtk_list_item_new();
457     gtk_misc_set_alignment (GTK_MISC (nl_lb), 0.0, 0.5);
458     gtk_container_add(GTK_CONTAINER(nl_item), nl_lb);
459     gtk_widget_show(nl_lb);
460     gtk_container_add(GTK_CONTAINER(column_l), nl_item);
461     gtk_widget_show(nl_item);
462     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_LBL_KEY, nl_lb);
463     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_NAME_KEY, clp);
464  
465     clp = clp->next;
466   }
467   
468   /* Colunm name entry and format selection */
469   tb = gtk_table_new(2, 2, FALSE);
470   gtk_container_add(GTK_CONTAINER(main_vb), tb);
471   gtk_table_set_row_spacings(GTK_TABLE(tb), 10);
472   gtk_table_set_col_spacings(GTK_TABLE(tb), 15);
473   gtk_widget_show(tb);
474   
475   lb = gtk_label_new("Column title:");
476   gtk_misc_set_alignment(GTK_MISC(lb), 1.0, 0.5);
477   gtk_table_attach_defaults(GTK_TABLE(tb), lb, 0, 1, 0, 1);
478   gtk_widget_show(lb);
479   
480   title_te = gtk_entry_new();
481   gtk_table_attach_defaults(GTK_TABLE(tb), title_te, 1, 2, 0, 1);
482   gtk_widget_show(title_te);
483
484   lb = gtk_label_new("Column format:");
485   gtk_misc_set_alignment(GTK_MISC(lb), 1.0, 0.5);
486   gtk_table_attach_defaults(GTK_TABLE(tb), lb, 0, 1, 1, 2);
487   gtk_widget_show(lb);
488
489   fmt_m = gtk_option_menu_new();
490   menu  = gtk_menu_new();
491   for (i = 0; i < NUM_COL_FMTS; i++) {
492     mitem = gtk_menu_item_new_with_label(col_format_desc(i));
493     gtk_menu_append(GTK_MENU(menu), mitem);
494     gtk_signal_connect_object( GTK_OBJECT(mitem), "activate",
495       GTK_SIGNAL_FUNC(column_set_fmt_cb), (gpointer) i);
496     gtk_widget_show(mitem);
497   }
498   gtk_option_menu_set_menu(GTK_OPTION_MENU(fmt_m), menu);
499   cur_fmt = 0;
500   gtk_option_menu_set_history(GTK_OPTION_MENU(fmt_m), cur_fmt);
501   gtk_table_attach_defaults(GTK_TABLE(tb), fmt_m, 1, 2, 1, 2);
502   gtk_widget_show(fmt_m);  
503       
504   return(main_vb);
505 }
506
507 static void
508 column_sel_list_cb(GtkWidget *l, gpointer data) {
509   fmt_data   *cfmt;
510   gchar      *title = "";
511   GList      *sl, *clp;
512   GtkObject  *l_item;
513   gint        sensitivity = FALSE, up_sens = FALSE, dn_sens = FALSE;
514
515   sl = GTK_LIST(l)->selection;
516           
517   if (sl) {  /* Something was selected */
518     l_item = GTK_OBJECT(sl->data);
519     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
520     if (clp) {
521       cfmt   = (fmt_data *) clp->data;
522       title   = cfmt->title;
523       cur_fmt = get_column_format_from_str(cfmt->fmt);
524       gtk_option_menu_set_history(GTK_OPTION_MENU(fmt_m), cur_fmt);
525       sensitivity = TRUE;
526       if (clp != g_list_first(prefs.col_list))
527         up_sens = TRUE;
528       if (clp != g_list_last(prefs.col_list))
529         dn_sens = TRUE;
530     }
531   }
532
533   /* Did you know that this function is called when the window is destroyed? */
534   /* Funny, that. */
535   if (!gtk_object_get_data(GTK_OBJECT(data), E_COL_CM_KEY)) {
536     gtk_entry_set_text(GTK_ENTRY(title_te), title);
537     gtk_widget_set_sensitive(chg_bt, sensitivity);
538     gtk_widget_set_sensitive(del_bt, sensitivity);
539     gtk_widget_set_sensitive(up_bt, up_sens);
540     gtk_widget_set_sensitive(dn_bt, dn_sens);
541   }
542 }
543
544 /* To do: add input checking to each of these callbacks */
545  
546 static void
547 column_sel_new_cb(GtkWidget *w, gpointer data) {
548   fmt_data   *cfmt;
549   gchar      *title;
550   GtkWidget  *nl_item, *nl_lb;
551   
552   title = gtk_entry_get_text(GTK_ENTRY(title_te));
553   
554   if (strlen(title) > 0) {
555     cfmt           = (fmt_data *) g_malloc(sizeof(fmt_data));
556     cfmt->title    = g_strdup(title);
557     cfmt->fmt      = g_strdup(col_format_to_string(cur_fmt));
558     prefs.col_list = g_list_append(prefs.col_list, cfmt);
559     nl_lb          = gtk_label_new(cfmt->title);
560     nl_item        = gtk_list_item_new();
561     gtk_misc_set_alignment (GTK_MISC (nl_lb), 0.0, 0.5);
562     gtk_container_add(GTK_CONTAINER(nl_item), nl_lb);
563     gtk_widget_show(nl_lb);
564     gtk_container_add(GTK_CONTAINER(column_l), nl_item);
565     gtk_widget_show(nl_item);
566     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_LBL_KEY, nl_lb);
567     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_NAME_KEY,
568       g_list_last(prefs.col_list));
569     gtk_list_select_child(GTK_LIST(column_l), nl_item);
570   }
571 }
572
573 static void
574 column_sel_chg_cb(GtkWidget *w, gpointer data) {
575   fmt_data   *cfmt;
576   gchar      *title = "";
577   GList      *sl, *clp;
578   GtkObject  *l_item;
579   GtkLabel   *nl_lb;
580
581   sl     = GTK_LIST(column_l)->selection;
582   title  = gtk_entry_get_text(GTK_ENTRY(title_te));
583
584   if (sl) {  /* Something was selected */
585     l_item = GTK_OBJECT(sl->data);
586     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
587     nl_lb  = (GtkLabel *) gtk_object_get_data(l_item, E_COL_LBL_KEY);
588     if (clp && nl_lb) {
589       cfmt = (fmt_data *) clp->data;
590       
591       if (strlen(title) > 0 && cfmt) {
592         g_free(cfmt->title);
593         g_free(cfmt->fmt);
594         cfmt->title = g_strdup(title);
595         cfmt->fmt   = g_strdup(col_format_to_string(cur_fmt));
596         gtk_label_set(nl_lb, cfmt->title);
597       }
598     }
599   }
600 }
601
602 static void
603 column_sel_del_cb(GtkWidget *w, gpointer data) {
604   GList      *sl, *clp;
605   fmt_data   *cfmt;
606   GtkObject  *l_item;
607   gint        pos;
608   
609   sl = GTK_LIST(column_l)->selection;
610   if (sl) {  /* Something was selected */
611     l_item = GTK_OBJECT(sl->data);
612     pos    = gtk_list_child_position(GTK_LIST(column_l), GTK_WIDGET(l_item));
613     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
614     if (clp) {
615       cfmt = (fmt_data *) clp->data;
616       g_free(cfmt->title);
617       g_free(cfmt->fmt);
618       g_free(cfmt);
619       prefs.col_list = g_list_remove_link(prefs.col_list, clp);
620       gtk_list_clear_items(GTK_LIST(column_l), pos, pos + 1);
621     } 
622   }
623 }
624
625 static void
626 column_sel_arrow_cb(GtkWidget *w, gpointer data) {
627   GList      *sl, *clp, *il;
628   fmt_data   *cfmt;
629   GtkObject  *l_item;
630   gint        pos, inc = 1;
631   
632   if (w == up_bt)
633     inc = -1;
634   
635   sl = GTK_LIST(column_l)->selection;
636   if (sl) {  /* Something was selected */
637     l_item  = GTK_OBJECT(sl->data);
638     pos     = gtk_list_child_position(GTK_LIST(column_l), GTK_WIDGET(l_item));
639     clp     = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
640     if (clp) {
641       cfmt = (fmt_data *) clp->data;
642       prefs.col_list = g_list_remove(prefs.col_list, cfmt);
643       g_list_insert(prefs.col_list, cfmt, pos + inc);
644       il = (GList *) g_malloc(sizeof(GList));
645       il->next = NULL;
646       il->prev = NULL;
647       il->data = l_item;
648       gtk_widget_ref(GTK_WIDGET(l_item));
649       gtk_list_clear_items(GTK_LIST(column_l), pos, pos + 1);
650       gtk_list_insert_items(GTK_LIST(column_l), il, pos + inc);
651       gtk_widget_unref(GTK_WIDGET(l_item));
652       gtk_list_select_item(GTK_LIST(column_l), pos + inc);
653     } 
654   }
655 }
656
657 void
658 column_set_fmt_cb(GtkWidget *w, gpointer data) {
659   cur_fmt = (gint) data;
660 }
661
662 void
663 column_prefs_ok(GtkWidget *w) {
664
665   column_prefs_cancel(w);
666 }
667
668 void
669 column_prefs_save(GtkWidget *w) {
670 }
671
672 void
673 column_prefs_cancel(GtkWidget *w) {
674  
675   /* Let the list cb know we're about to destroy the widget tree, so it */
676   /* doesn't operate on widgets that don't exist. */  
677   gtk_object_set_data(GTK_OBJECT(w), E_COL_CM_KEY, (gpointer)TRUE);
678   gtk_widget_destroy(GTK_WIDGET(w));
679