Include <time.h> to declare "gmtime()".
[obnox/wireshark/wip.git] / column.c
1 /* column.c
2  * Routines for handling column preferences
3  *
4  * $Id: column.c,v 1.26 1999/12/29 10:48:01 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
40 #ifdef HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43
44 #include <sys/stat.h>
45
46 #include "timestamp.h"
47 #include "prefs.h"
48 #include "column.h"
49 #include "packet.h"
50
51 static GtkWidget *column_l, *chg_bt, *del_bt, *title_te, *fmt_m, *up_bt,
52                  *dn_bt;
53 static gint       cur_fmt;
54
55 #define E_COL_NAME_KEY "column_name"
56 #define E_COL_LBL_KEY  "column_label"
57 #define E_COL_CM_KEY   "in_col_cancel_mode"
58
59 static gchar *col_format_to_string(gint);
60 static gchar *col_format_desc(gint);
61 static gint   get_column_format_from_str(gchar *str);
62 static void   column_sel_list_cb(GtkWidget *, gpointer);
63 static void   column_sel_new_cb(GtkWidget *, gpointer);
64 static void   column_sel_chg_cb(GtkWidget *, gpointer);
65 static void   column_sel_del_cb(GtkWidget *, gpointer);
66 static void   column_sel_arrow_cb(GtkWidget *, gpointer);
67 static void   column_set_fmt_cb(GtkWidget *, gpointer);
68
69 /* Given a format number (as defined in packet.h), returns its equivalent
70    string */
71 static gchar *
72 col_format_to_string(gint fmt) {
73   gchar *slist[] = { "%m", "%t", "%Rt", "%At", "%Tt", "%s", "%rs", "%us",
74                      "%hs", "%rhs", "%uhs", "%ns", "%rns", "%uns", "%d",
75                      "%rd", "%ud", "%hd", "%rhd", "%uhd", "%nd", "%rnd",
76                      "%und", "%S", "%rS", "%uS", "%D", "%rD", "%uD", "%p",
77                      "%i", "%L" };
78   
79   if (fmt < 0 || fmt > NUM_COL_FMTS)
80     return NULL;
81   
82   return(slist[fmt]);
83 }
84
85 /* Given a format number (as defined in packet.h), returns its
86   description */
87 static gchar *
88 col_format_desc(gint fmt) {
89   gchar *dlist[] = { "Number", "Time (command line specified)",
90                      "Relative time", "Absolute time", "Delta time",
91                      "Source address", "Src addr (resolved)",
92                      "Src addr (unresolved)", "Hardware src addr",
93                      "Hw src addr (resolved)", "Hw src addr (unresolved)",
94                      "Network src addr", "Net src addr (resolved)",
95                      "Net src addr (unresolved)", "Destination address",
96                      "Dest addr (resolved)", "Dest addr (unresolved)",
97                      "Hardware dest addr", "Hw dest addr (resolved)",
98                      "Hw dest addr (unresolved)", "Network dest addr",
99                      "Net dest addr (resolved)", "Net dest addr (unresolved)",
100                      "Source port", "Src port (resolved)",
101                      "Src port (unresolved)", "Destination port",
102                      "Dest port (resolved)", "Dest port (unresolved)",
103                      "Protocol", "Information", "Packet length (bytes)" };
104   
105   if (fmt < 0 || fmt > NUM_COL_FMTS)
106     return NULL;
107   
108   return(dlist[fmt]);
109 }
110
111 /* Marks each array element true if it can be substituted for the given
112    column format */
113 void
114 get_column_format_matches(gboolean *fmt_list, gint format) {
115   int i;
116   
117   for (i = 0; i < NUM_COL_FMTS; i++) {
118     /* Get the obvious: the format itself */
119     if (i == format)
120       fmt_list[i] = TRUE;
121     /* Get any formats lower down on the chain */
122     switch (format) {
123       case COL_DEF_SRC:
124         fmt_list[COL_RES_DL_SRC] = TRUE;
125         fmt_list[COL_RES_NET_SRC] = TRUE;
126         break;
127       case COL_RES_SRC:
128         fmt_list[COL_RES_DL_SRC] = TRUE;
129         fmt_list[COL_RES_NET_SRC] = TRUE;
130         break;
131       case COL_UNRES_SRC:
132         fmt_list[COL_UNRES_DL_SRC] = TRUE;
133         fmt_list[COL_UNRES_NET_SRC] = TRUE;
134         break;
135       case COL_DEF_DST:
136         fmt_list[COL_RES_DL_DST] = TRUE;
137         fmt_list[COL_RES_NET_DST] = TRUE;
138         break;
139       case COL_RES_DST:
140         fmt_list[COL_RES_DL_DST] = TRUE;
141         fmt_list[COL_RES_NET_DST] = TRUE;
142         break;
143       case COL_UNRES_DST:
144         fmt_list[COL_UNRES_DL_DST] = TRUE;
145         fmt_list[COL_UNRES_NET_DST] = TRUE;
146         break;
147       case COL_DEF_DL_SRC:
148         fmt_list[COL_RES_DL_SRC] = TRUE;
149         break;
150       case COL_DEF_DL_DST:
151         fmt_list[COL_RES_DL_DST] = TRUE;
152         break;
153       case COL_DEF_NET_SRC:
154         fmt_list[COL_RES_NET_SRC] = TRUE;
155         break;
156       case COL_DEF_NET_DST:
157         fmt_list[COL_RES_NET_DST] = TRUE;
158         break;
159       case COL_DEF_SRC_PORT:
160         fmt_list[COL_RES_SRC_PORT] = TRUE;
161         break;
162       case COL_DEF_DST_PORT:
163         fmt_list[COL_RES_DST_PORT] = TRUE;
164         break;
165       default:
166         break;
167     }
168   }
169 }
170
171 /* Returns a string representing the longest possible value for a
172    particular column type. */
173 static char *
174 get_column_longest_string(gint format)
175 {
176   switch (format) {
177     case COL_NUMBER:
178       return "0000000";
179       break;
180     case COL_CLS_TIME:
181       if (timestamp_type == ABSOLUTE)
182         return "00:00:00.000000";
183       else
184         return "0000.000000";
185       break;
186     case COL_ABS_TIME:
187       return "00:00:00.000000";
188       break;
189     case COL_REL_TIME:
190     case COL_DELTA_TIME:
191       return "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 "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 "000000";
220       break;
221     case COL_PROTOCOL:
222       return "NetBIOS"; /* not the longest, but the longest is too long */
223       break;
224     case COL_PACKET_LENGTH:
225       return "000000";
226       break;
227     default: /* COL_INFO */
228       return "Source port: kerberos-master  Destination port: kerberos-master";
229       break;
230   }
231 }
232
233 /* Returns the longest possible width, using the specified font,
234    for a particular column type.
235
236    Except for the COL...SRC and COL...DST columns, these are used
237    only when a capture is being displayed while it's taking place;
238    they are arguably somewhat fragile, as changes to the code that
239    generates them don't cause these widths to change, but that's
240    probably not too big a problem, given that the sizes are
241    recomputed based on the actual data in the columns when the capture
242    is done, and given that the width for COL...SRC and COL...DST columns
243    is somewhat arbitrary in any case.  We should probably clean
244    that up eventually, though. */
245 gint
246 get_column_width(gint format, GdkFont *font)
247 {
248   return (gdk_string_width(font, get_column_longest_string(format)));
249 }
250
251 /* Returns the longest possible width, in characters, for a particular
252    column type. */
253 gint
254 get_column_char_width(gint format)
255 {
256   return strlen(get_column_longest_string(format));
257 }
258
259 enum col_resize_type
260 get_column_resize_type(gint format) {
261   switch (format) {
262     case COL_NUMBER:
263     case COL_CLS_TIME:
264     case COL_ABS_TIME:
265     case COL_REL_TIME:
266     case COL_DELTA_TIME:
267     case COL_DEF_SRC_PORT:
268     case COL_RES_SRC_PORT:
269     case COL_UNRES_SRC_PORT:
270     case COL_DEF_DST_PORT:
271     case COL_RES_DST_PORT:
272     case COL_UNRES_DST_PORT:
273     case COL_PROTOCOL:
274     case COL_PACKET_LENGTH:
275       /* We don't want these to resize during a live capture, as that
276          gets in the way of trying to look at the data while it's being
277          captured. */
278       return (RESIZE_AUTO);
279       break;
280     case COL_DEF_SRC:
281     case COL_RES_SRC:
282     case COL_UNRES_SRC:
283     case COL_DEF_DL_SRC:
284     case COL_RES_DL_SRC:
285     case COL_UNRES_DL_SRC:
286     case COL_DEF_NET_SRC:
287     case COL_RES_NET_SRC:
288     case COL_UNRES_NET_SRC:
289     case COL_DEF_DST:
290     case COL_RES_DST:
291     case COL_UNRES_DST:
292     case COL_DEF_DL_DST:
293     case COL_RES_DL_DST:
294     case COL_UNRES_DL_DST:
295     case COL_DEF_NET_DST:
296     case COL_RES_NET_DST:
297     case COL_UNRES_NET_DST:
298       /* We don't want these to resize dynamically; if they get resolved
299          to names, those names could be very long, and auto-resizing
300          columns showing those names may leave too little room for
301          other columns such as the "Info" column. */
302       return (RESIZE_MANUAL);
303       break;
304     default: /* COL_INFO */
305       /* We want this to resize dynamically, even during a live capture,
306          because otherewise you won't be able to see all that's in
307          it. */
308       return (RESIZE_LIVE);
309       break;
310   }
311 }
312
313 #define TIME_DEF 0
314 #define TIME_REL 1
315 #define TIME_ABS 2
316 #define TIME_DEL 3
317
318 #define RES_DEF  0
319 #define RES_DO   1
320 #define RES_DONT 2
321
322 #define ADDR_DEF 0
323 #define ADDR_DL  3
324 #define ADDR_NET 6
325
326 gint
327 get_column_format(gint col) {
328   GList    *clp = g_list_nth(prefs.col_list, col);
329   fmt_data *cfmt;
330   
331   cfmt = (fmt_data *) clp->data;
332   
333   return(get_column_format_from_str(cfmt->fmt));
334 }
335
336 static gint
337 get_column_format_from_str(gchar *str) {
338   gchar *cptr = str;
339   gint      res_off = RES_DEF, addr_off = ADDR_DEF, time_off = TIME_DEF;
340
341   /* To do: Make this parse %-formatted strings "for real" */
342   while (*cptr != '\0') {
343     switch (*cptr) {
344       case 't':  /* To do: fix for absolute and delta */
345         return COL_CLS_TIME + time_off;
346         break;
347       case 'm':
348         return COL_NUMBER;
349         break;
350       case 's':
351         return COL_DEF_SRC + res_off + addr_off;
352         break;
353       case 'd':
354         return COL_DEF_DST + res_off + addr_off;
355         break;
356       case 'S':
357         return COL_DEF_SRC_PORT + res_off;
358         break;
359       case 'D':
360         return COL_DEF_DST_PORT + res_off;
361         break;
362       case 'p':
363         return COL_PROTOCOL;
364         break;
365       case 'i':
366         return COL_INFO;
367         break;
368       case 'r':
369         res_off = RES_DO;
370         break;
371       case 'u':
372         res_off = RES_DONT;
373         break;
374       case 'h':
375         addr_off = ADDR_DL;
376         break;
377       case 'n':
378         addr_off = ADDR_NET;
379         break;
380       case 'R':
381         time_off = TIME_REL;
382         break;
383       case 'A':
384         time_off = TIME_ABS;
385         break;
386       case 'T':
387         time_off = TIME_DEL;
388         break;
389       case 'L':
390         return COL_PACKET_LENGTH;
391         break;
392     }
393     cptr++;
394   }
395   return COL_NUMBER;
396 }
397
398 gchar *
399 get_column_title(gint col) {
400   GList    *clp = g_list_nth(prefs.col_list, col);
401   fmt_data *cfmt;
402   
403   cfmt = (fmt_data *) clp->data;
404
405   return(cfmt->title);  
406 }
407
408 #define MAX_FMT_PREF_LEN      1024
409 #define MAX_FMT_PREF_LINE_LEN   60
410 gchar *
411 col_format_to_pref_str() {
412   static gchar  pref_str[MAX_FMT_PREF_LEN] = "";
413   GList        *clp = g_list_first(prefs.col_list);
414   fmt_data     *cfmt;
415   int           cur_pos = 0, cur_len = 0, fmt_len;
416   
417   while (clp) {
418     cfmt = (fmt_data *) clp->data;
419     
420     fmt_len = strlen(cfmt->title) + 4;
421     if ((fmt_len + cur_len) < (MAX_FMT_PREF_LEN - 1)) {
422       if ((fmt_len + cur_pos) > MAX_FMT_PREF_LINE_LEN) {
423         cur_len--;
424         cur_pos = 0;
425                 pref_str[cur_len] = '\n'; cur_len++;
426         pref_str[cur_len] = '\t'; cur_len++;
427       }
428       sprintf(&pref_str[cur_len], "\"%s\", ", cfmt->title);
429       cur_len += fmt_len;
430       cur_pos += fmt_len;
431     }
432
433     fmt_len = strlen(cfmt->fmt) + 4;
434     if ((fmt_len + cur_len) < (MAX_FMT_PREF_LEN - 1)) {
435       if ((fmt_len + cur_pos) > MAX_FMT_PREF_LINE_LEN) {
436         cur_len--;
437         cur_pos = 0;
438         pref_str[cur_len] = '\n'; cur_len++;
439         pref_str[cur_len] = '\t'; cur_len++;
440       }
441       sprintf(&pref_str[cur_len], "\"%s\", ", cfmt->fmt);
442       cur_len += fmt_len;
443       cur_pos += fmt_len;
444     }
445     
446     clp = clp->next;
447   }
448   
449   if (cur_len > 2)
450     pref_str[cur_len - 2] = '\0';
451
452   return(pref_str);
453 }    
454
455 /* Create and display the column selection widgets. */
456 /* Called when the 'Columns' preference notebook page is selected. */
457 GtkWidget *
458 column_prefs_show() {
459   GtkWidget   *main_vb, *top_hb, *list_bb, *new_bt, *column_sc, *nl_item,
460               *nl_lb, *tb, *lb, *menu, *mitem, *arrow_hb;
461   GList       *clp = NULL;
462   fmt_data    *cfmt;
463   gint         i;
464
465   /* Container for each row of widgets */
466   main_vb = gtk_vbox_new(FALSE, 5);
467   gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
468   gtk_widget_show(main_vb);
469   gtk_object_set_data(GTK_OBJECT(main_vb), E_COL_CM_KEY, (gpointer)FALSE);
470   
471   /* Top row: Column list and buttons */
472   top_hb = gtk_hbox_new(FALSE, 5);
473   gtk_container_add(GTK_CONTAINER(main_vb), top_hb);
474   gtk_widget_show(top_hb);
475   
476   list_bb = gtk_vbutton_box_new();
477   gtk_button_box_set_layout (GTK_BUTTON_BOX (list_bb), GTK_BUTTONBOX_START);
478   gtk_container_add(GTK_CONTAINER(top_hb), list_bb);
479   gtk_widget_show(list_bb);
480
481   new_bt = gtk_button_new_with_label ("New");
482   gtk_signal_connect(GTK_OBJECT(new_bt), "clicked",
483     GTK_SIGNAL_FUNC(column_sel_new_cb), NULL);
484   gtk_container_add(GTK_CONTAINER(list_bb), new_bt);
485   gtk_widget_show(new_bt);
486   
487   chg_bt = gtk_button_new_with_label ("Change");
488   gtk_widget_set_sensitive(chg_bt, FALSE);
489   gtk_signal_connect(GTK_OBJECT(chg_bt), "clicked",
490     GTK_SIGNAL_FUNC(column_sel_chg_cb), NULL);
491   gtk_container_add(GTK_CONTAINER(list_bb), chg_bt);
492   gtk_widget_show(chg_bt);
493   
494   del_bt = gtk_button_new_with_label ("Delete");
495   gtk_widget_set_sensitive(del_bt, FALSE);
496   gtk_signal_connect(GTK_OBJECT(del_bt), "clicked",
497     GTK_SIGNAL_FUNC(column_sel_del_cb), NULL);
498   gtk_container_add(GTK_CONTAINER(list_bb), del_bt);
499   gtk_widget_show(del_bt);
500   
501   arrow_hb = gtk_hbox_new(TRUE, 3);
502   gtk_container_add(GTK_CONTAINER(list_bb), arrow_hb);
503   gtk_widget_show(arrow_hb);
504   
505   up_bt = gtk_button_new_with_label("Up");
506   gtk_widget_set_sensitive(up_bt, FALSE);
507   gtk_signal_connect(GTK_OBJECT(up_bt), "clicked",
508     GTK_SIGNAL_FUNC(column_sel_arrow_cb), NULL);
509   gtk_box_pack_start(GTK_BOX(arrow_hb), up_bt, TRUE, TRUE, 0);
510   gtk_widget_show(up_bt);
511   
512   dn_bt = gtk_button_new_with_label("Down");
513   gtk_widget_set_sensitive(dn_bt, FALSE);
514   gtk_signal_connect(GTK_OBJECT(dn_bt), "clicked",
515     GTK_SIGNAL_FUNC(column_sel_arrow_cb), NULL);
516   gtk_box_pack_start(GTK_BOX(arrow_hb), dn_bt, TRUE, TRUE, 0);
517   gtk_widget_show(dn_bt);
518   
519   column_sc = gtk_scrolled_window_new(NULL, NULL);
520   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(column_sc),
521     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
522   gtk_widget_set_usize(column_sc, 250, 150);
523   gtk_container_add(GTK_CONTAINER(top_hb), column_sc);
524   gtk_widget_show(column_sc);
525
526   column_l = gtk_list_new();
527   gtk_list_set_selection_mode(GTK_LIST(column_l), GTK_SELECTION_SINGLE);
528   gtk_signal_connect(GTK_OBJECT(column_l), "selection_changed",
529     GTK_SIGNAL_FUNC(column_sel_list_cb), main_vb);
530   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(column_sc), column_l);
531   gtk_widget_show(column_l);
532
533   clp = g_list_first(prefs.col_list);
534   while (clp) {
535     cfmt    = (fmt_data *) clp->data;
536     nl_lb   = gtk_label_new(cfmt->title);
537     nl_item = gtk_list_item_new();
538     gtk_misc_set_alignment (GTK_MISC (nl_lb), 0.0, 0.5);
539     gtk_container_add(GTK_CONTAINER(nl_item), nl_lb);
540     gtk_widget_show(nl_lb);
541     gtk_container_add(GTK_CONTAINER(column_l), nl_item);
542     gtk_widget_show(nl_item);
543     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_LBL_KEY, nl_lb);
544     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_NAME_KEY, clp);
545  
546     clp = clp->next;
547   }
548   
549   /* Colunm name entry and format selection */
550   tb = gtk_table_new(2, 2, FALSE);
551   gtk_container_add(GTK_CONTAINER(main_vb), tb);
552   gtk_table_set_row_spacings(GTK_TABLE(tb), 10);
553   gtk_table_set_col_spacings(GTK_TABLE(tb), 15);
554   gtk_widget_show(tb);
555   
556   lb = gtk_label_new("Column title:");
557   gtk_misc_set_alignment(GTK_MISC(lb), 1.0, 0.5);
558   gtk_table_attach_defaults(GTK_TABLE(tb), lb, 0, 1, 0, 1);
559   gtk_widget_show(lb);
560   
561   title_te = gtk_entry_new();
562   gtk_table_attach_defaults(GTK_TABLE(tb), title_te, 1, 2, 0, 1);
563   gtk_widget_show(title_te);
564
565   lb = gtk_label_new("Column format:");
566   gtk_misc_set_alignment(GTK_MISC(lb), 1.0, 0.5);
567   gtk_table_attach_defaults(GTK_TABLE(tb), lb, 0, 1, 1, 2);
568   gtk_widget_show(lb);
569
570   fmt_m = gtk_option_menu_new();
571   menu  = gtk_menu_new();
572   for (i = 0; i < NUM_COL_FMTS; i++) {
573     mitem = gtk_menu_item_new_with_label(col_format_desc(i));
574     gtk_menu_append(GTK_MENU(menu), mitem);
575     gtk_signal_connect( GTK_OBJECT(mitem), "activate",
576       GTK_SIGNAL_FUNC(column_set_fmt_cb), (gpointer) i);
577     gtk_widget_show(mitem);
578   }
579   gtk_option_menu_set_menu(GTK_OPTION_MENU(fmt_m), menu);
580   cur_fmt = 0;
581   gtk_option_menu_set_history(GTK_OPTION_MENU(fmt_m), cur_fmt);
582   gtk_table_attach_defaults(GTK_TABLE(tb), fmt_m, 1, 2, 1, 2);
583   gtk_widget_show(fmt_m);  
584       
585   return(main_vb);
586 }
587
588 static void
589 column_sel_list_cb(GtkWidget *l, gpointer data) {
590   fmt_data   *cfmt;
591   gchar      *title = "";
592   GList      *sl, *clp;
593   GtkObject  *l_item;
594   gint        sensitivity = FALSE, up_sens = FALSE, dn_sens = FALSE;
595
596   sl = GTK_LIST(l)->selection;
597           
598   if (sl) {  /* Something was selected */
599     l_item = GTK_OBJECT(sl->data);
600     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
601     if (clp) {
602       cfmt   = (fmt_data *) clp->data;
603       title   = cfmt->title;
604       cur_fmt = get_column_format_from_str(cfmt->fmt);
605       gtk_option_menu_set_history(GTK_OPTION_MENU(fmt_m), cur_fmt);
606       sensitivity = TRUE;
607       if (clp != g_list_first(prefs.col_list))
608         up_sens = TRUE;
609       if (clp != g_list_last(prefs.col_list))
610         dn_sens = TRUE;
611     }
612   }
613
614   /* Did you know that this function is called when the window is destroyed? */
615   /* Funny, that. */
616   if (!gtk_object_get_data(GTK_OBJECT(data), E_COL_CM_KEY)) {
617     gtk_entry_set_text(GTK_ENTRY(title_te), title);
618     gtk_widget_set_sensitive(chg_bt, sensitivity);
619     gtk_widget_set_sensitive(del_bt, sensitivity);
620     gtk_widget_set_sensitive(up_bt, up_sens);
621     gtk_widget_set_sensitive(dn_bt, dn_sens);
622   }
623 }
624
625 /* To do: add input checking to each of these callbacks */
626  
627 static void
628 column_sel_new_cb(GtkWidget *w, gpointer data) {
629   fmt_data   *cfmt;
630   gchar      *title;
631   GtkWidget  *nl_item, *nl_lb;
632   
633   title = gtk_entry_get_text(GTK_ENTRY(title_te));
634   
635   if (strlen(title) > 0) {
636     cfmt           = (fmt_data *) g_malloc(sizeof(fmt_data));
637     cfmt->title    = g_strdup(title);
638     cfmt->fmt      = g_strdup(col_format_to_string(cur_fmt));
639     prefs.col_list = g_list_append(prefs.col_list, cfmt);
640     nl_lb          = gtk_label_new(cfmt->title);
641     nl_item        = gtk_list_item_new();
642     gtk_misc_set_alignment (GTK_MISC (nl_lb), 0.0, 0.5);
643     gtk_container_add(GTK_CONTAINER(nl_item), nl_lb);
644     gtk_widget_show(nl_lb);
645     gtk_container_add(GTK_CONTAINER(column_l), nl_item);
646     gtk_widget_show(nl_item);
647     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_LBL_KEY, nl_lb);
648     gtk_object_set_data(GTK_OBJECT(nl_item), E_COL_NAME_KEY,
649       g_list_last(prefs.col_list));
650     gtk_list_select_child(GTK_LIST(column_l), nl_item);
651   }
652 }
653
654 static void
655 column_sel_chg_cb(GtkWidget *w, gpointer data) {
656   fmt_data   *cfmt;
657   gchar      *title = "";
658   GList      *sl, *clp;
659   GtkObject  *l_item;
660   GtkLabel   *nl_lb;
661
662   sl     = GTK_LIST(column_l)->selection;
663   title  = gtk_entry_get_text(GTK_ENTRY(title_te));
664
665   if (sl) {  /* Something was selected */
666     l_item = GTK_OBJECT(sl->data);
667     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
668     nl_lb  = (GtkLabel *) gtk_object_get_data(l_item, E_COL_LBL_KEY);
669     if (clp && nl_lb) {
670       cfmt = (fmt_data *) clp->data;
671       
672       if (strlen(title) > 0 && cfmt) {
673         g_free(cfmt->title);
674         g_free(cfmt->fmt);
675         cfmt->title = g_strdup(title);
676         cfmt->fmt   = g_strdup(col_format_to_string(cur_fmt));
677         gtk_label_set(nl_lb, cfmt->title);
678       }
679     }
680   }
681 }
682
683 static void
684 column_sel_del_cb(GtkWidget *w, gpointer data) {
685   GList      *sl, *clp;
686   fmt_data   *cfmt;
687   GtkObject  *l_item;
688   gint        pos;
689   
690   sl = GTK_LIST(column_l)->selection;
691   if (sl) {  /* Something was selected */
692     l_item = GTK_OBJECT(sl->data);
693     pos    = gtk_list_child_position(GTK_LIST(column_l), GTK_WIDGET(l_item));
694     clp    = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
695     if (clp) {
696       cfmt = (fmt_data *) clp->data;
697       g_free(cfmt->title);
698       g_free(cfmt->fmt);
699       g_free(cfmt);
700       prefs.col_list = g_list_remove_link(prefs.col_list, clp);
701       gtk_list_clear_items(GTK_LIST(column_l), pos, pos + 1);
702     } 
703   }
704 }
705
706 static void
707 column_sel_arrow_cb(GtkWidget *w, gpointer data) {
708   GList      *sl, *clp, *il;
709   fmt_data   *cfmt;
710   GtkObject  *l_item;
711   gint        pos, inc = 1;
712   
713   if (w == up_bt)
714     inc = -1;
715   
716   sl = GTK_LIST(column_l)->selection;
717   if (sl) {  /* Something was selected */
718     l_item  = GTK_OBJECT(sl->data);
719     pos     = gtk_list_child_position(GTK_LIST(column_l), GTK_WIDGET(l_item));
720     clp     = (GList *) gtk_object_get_data(l_item, E_COL_NAME_KEY);
721     if (clp) {
722       cfmt = (fmt_data *) clp->data;
723       prefs.col_list = g_list_remove(prefs.col_list, cfmt);
724       g_list_insert(prefs.col_list, cfmt, pos + inc);
725       il = (GList *) g_malloc(sizeof(GList));
726       il->next = NULL;
727       il->prev = NULL;
728       il->data = l_item;
729       gtk_widget_ref(GTK_WIDGET(l_item));
730       gtk_list_clear_items(GTK_LIST(column_l), pos, pos + 1);
731       gtk_list_insert_items(GTK_LIST(column_l), il, pos + inc);
732       gtk_widget_unref(GTK_WIDGET(l_item));
733       gtk_list_select_item(GTK_LIST(column_l), pos + inc);
734     } 
735   }
736 }
737
738 void
739 column_set_fmt_cb(GtkWidget *w, gpointer data) {
740   cur_fmt = (gint) data;
741 }
742
743 void
744 column_prefs_ok(GtkWidget *w) {
745
746   column_prefs_delete(w);
747 }
748
749 void
750 column_prefs_save(GtkWidget *w) {
751 }
752
753 void
754 column_prefs_cancel(GtkWidget *w) {
755
756   column_prefs_delete(w);
757 }
758
759 void
760 column_prefs_delete(GtkWidget *w) {
761  
762   /* Let the list cb know we're about to destroy the widget tree, so it */
763   /* doesn't operate on widgets that don't exist. */  
764   gtk_object_set_data(GTK_OBJECT(w), E_COL_CM_KEY, (gpointer)TRUE);
765   gtk_widget_destroy(GTK_WIDGET(w));
766