Instead of using private #defines for the last argument to
[obnox/wireshark/wip.git] / gtk / gui_utils.c
1 /* gui_utils.c
2  * UI utility routines, some GTK+-specific (declared in gtk/gui_utils.h)
3  * and some with GUI-independent APIs, with this file containing the GTK+
4  * implementations of them (declared in ui_util.h)
5  *
6  * $Id$
7  *
8  * Wireshark - Network traffic analyzer
9  * By Gerald Combs <gerald@wireshark.org>
10  * Copyright 1998 Gerald Combs
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <string.h>
32 #include <locale.h>
33
34 #include <gtk/gtk.h>
35 #include <gdk/gdkkeysyms.h>
36
37 #include <epan/prefs.h>
38 #include "epan/epan.h"
39
40 #include <epan/packet_info.h>
41 #include "../ui_util.h"
42 #include <wsutil/file_util.h>
43
44 #include "gtk/gtkglobals.h"
45 #include "gtk/gui_utils.h"
46 #include "gtk/font_utils.h"
47 #include "gtk/recent.h"
48
49 #include "image/wsicon16.xpm"
50
51 #ifdef _WIN32
52 #include <windows.h>
53 #endif
54
55 #define WINDOW_GEOM_KEY "window_geom"
56
57
58 /* load the geometry values for a window from previously saved values */
59 static gboolean window_geom_load(const gchar *name, window_geometry_t *geom);
60
61
62
63 /* Set our window icon.  The GDK documentation doesn't provide any
64    actual documentation for gdk_window_set_icon(), so we'll steal
65    libgimp/gimpdialog.c:gimp_dialog_realize_callback() from the Gimp
66    sources and assume it's safe.
67
68    XXX - The current icon size is fixed at 16x16 pixels, which looks fine
69    with kwm (KDE 1.x's window manager), Sawfish (the "default" window
70    manager for GNOME?), and under Windows with Exceed putting X windows
71    on the Windows desktop, using Exceed as the window manager, as those
72    window managers put a 16x16 icon on the title bar.
73
74    The window managers in some windowing environments (e.g. dtwm in CDE)
75    and some stand-alone window managers have larger icon sizes (many window
76    managers put the window icon on the desktop, in the Windows 3.x style,
77    rather than in the titlebar, in the Windows 4.x style), so we need to
78    find a way to size our icon appropriately.
79
80    The X11 Inter-Client Communications Conventions Manual, Version 1.1,
81    in X11R5, specifies that "a window manager that wishes to place
82    constraints on the sizes of icon pixmaps and/or windows should
83    place a property called WM_ICON_SIZE on the root"; that property
84    contains minimum width and height, maximum width and height, and
85    width and height increment values.  "XGetIconSizes()" retrieves
86    that property; unfortunately, I've yet to find a window manager
87    that sets it on the root window (kwm, AfterStep, and Exceed don't
88    appear to set it).
89
90    The X Desktop Group's Window Manager Standard specifies, in the section
91    on Application Window Properties, an _NET_WM_ICON property, presumably
92    set by the window manager, which is an array of possible icon sizes
93    for the client.  There's no API in GTK+ 1.2[.x] for this; there may
94    eventually be one either in GTK+ 2.0 or GNOME 2.0.
95
96    Some window managers can be configured to take the window name
97    specified by the WM_NAME property of a window or the resource
98    or class name specified by the WM_CLASS property and base the
99    choice of icon for the window on one of those; WM_CLASS for
100    Wireshark's windows has a resource name of "wireshark" and a class
101    name of "Wireshark".  However, the way that's done is window-manager-
102    specific, and there's no way to determine what size a particular
103    window manager would want, so there's no way to automate this as
104    part of the installation of Wireshark.
105    */
106 static void
107 window_icon_realize_cb (GtkWidget *win, gpointer data _U_)
108 {
109 #ifndef _WIN32
110   static GdkPixmap *icon_pmap = NULL;
111   static GdkBitmap *icon_mask = NULL;
112   GtkStyle         *style;
113
114   style = gtk_widget_get_style (win);
115
116   if (icon_pmap == NULL) {
117     icon_pmap = gdk_pixmap_create_from_xpm_d (win->window,
118                 &icon_mask, &style->bg[GTK_STATE_NORMAL],
119                 (gchar **) wsicon16_xpm);
120   }
121
122   gdk_window_set_icon (win->window, NULL, icon_pmap, icon_mask);
123 #endif
124 }
125
126
127 /* Create a new window, of the specified type, with the specified title
128    (if any) and the Wireshark icon. */
129 GtkWidget *
130 window_new(GtkWindowType type, const gchar *title)
131 {
132   GtkWidget *win;
133
134   win = gtk_window_new(type);
135   if (title != NULL)
136     gtk_window_set_title(GTK_WINDOW(win), title);
137   g_signal_connect(win, "realize", G_CALLBACK(window_icon_realize_cb), NULL);
138
139   /* XXX - which one is the correct default policy? or use a preference for this? */
140   /* GTK_WIN_POS_NONE, GTK_WIN_POS_CENTER or GTK_WIN_POS_MOUSE */
141   /* a lot of people dislike GTK_WIN_POS_MOUSE */
142
143   /* set the initial position (must be done, before show is called!) */
144 /*  gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);*/
145   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_NONE);
146
147   return win;
148 }
149
150
151 /* Same as window_new(), but will keep it's geometry values (size, position, ...).
152  * Be sure to use window_present() and window_destroy() appropriately! */
153 GtkWidget *
154 window_new_with_geom(GtkWindowType type, const gchar *title, const gchar *geom_name)
155 {
156   window_geometry_t geom;
157   GtkWidget *win = window_new(type, title);
158
159   g_object_set_data(G_OBJECT(win), WINDOW_GEOM_KEY, (gpointer)g_strdup(geom_name));
160
161   /* do we have a previously saved size and position of this window? */
162   if(geom_name) {
163     /* It's a good idea to set the position and size of the window already here,
164      * as it's still invisible and won't "flicker the screen" while initially resizing. */
165     if(window_geom_load(geom_name, &geom)) {
166       /* XXX - use prefs to select which values to set? */
167       geom.set_pos        = TRUE;
168       geom.set_size       = TRUE;
169       geom.set_maximized  = FALSE;  /* don't maximize until window is shown */
170       window_set_geometry(win, &geom);
171     }
172   }
173
174   return win;
175 }
176
177
178 /* Create a new window for a splash screen; it's a main window, without decoration,
179    positioned in the center of the screen. */
180 GtkWidget *
181 splash_window_new(void)
182 {
183   GtkWidget *win;
184
185   win = window_new(GTK_WINDOW_TOPLEVEL, "Wireshark");
186   gtk_window_set_decorated(GTK_WINDOW(win), FALSE);
187
188   /* set the initial position (must be done, before show is called!) */
189   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);
190
191   return win;
192 }
193
194
195 /* Present the created window on the screen. */
196 void
197 window_present(GtkWidget *win)
198 {
199   window_geometry_t geom;
200   const gchar *name;
201
202   /* present this window */
203   gtk_window_present(GTK_WINDOW(win));
204
205   /* do we have a previously saved size and position of this window? */
206   name = g_object_get_data(G_OBJECT(win), WINDOW_GEOM_KEY);
207   if(name) {
208     if(window_geom_load(name, &geom)) {
209       /* XXX - use prefs to select which values to set? */
210       geom.set_pos        = TRUE;
211       geom.set_size       = TRUE;
212       geom.set_maximized  = TRUE;
213       window_set_geometry(win, &geom);
214     }
215   }
216 }
217
218
219 static gint
220 window_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer cancel_button)
221 {
222   g_return_val_if_fail (widget != NULL, FALSE);
223   g_return_val_if_fail (event != NULL, FALSE);
224
225   if (event->keyval == GDK_Escape) {
226     gtk_widget_activate(GTK_WIDGET(cancel_button));
227     return TRUE;
228   }
229
230   return FALSE;
231 }
232
233
234 /* Set the "key_press_event" signal for a top-level dialog window to
235    call a routine to activate the "Cancel" button for a dialog box if
236    the key being pressed is the <Esc> key.
237
238    XXX - there should be a GTK+ widget that'll do that for you, and
239    let you specify a "Cancel" button.  It should also not impose
240    a requirement that there be a separator in the dialog box, as
241    the GtkDialog widget does; the visual convention that there's
242    such a separator between the rest of the dialog boxes and buttons
243    such as "OK" and "Cancel" is, for better or worse, not universal
244    (not even in GTK+ - look at the GtkFileSelection dialog!). */
245 static void
246 window_set_cancel(GtkWidget *widget, GtkWidget *cancel_button)
247 {
248   g_signal_connect(widget, "key_press_event", G_CALLBACK(window_key_press_cb), cancel_button);
249 }
250
251
252 /* set the actions needed for the cancel "Close"/"Ok"/"Cancel" button that closes the window */
253 void window_set_cancel_button(GtkWidget *win, GtkWidget *bt, window_cancel_button_fct cb)
254 {
255   if(cb)
256     g_signal_connect(bt, "clicked", G_CALLBACK(cb), win);
257
258   gtk_widget_grab_default(bt);
259
260   window_set_cancel(win, bt);
261 }
262
263
264 /* default callback handler for cancel button "clicked" signal */
265 void window_cancel_button_cb(GtkWidget *w _U_, gpointer data)
266 {
267   window_destroy(GTK_WIDGET(data));
268 }
269
270
271 /* default callback handler: the window managers X of the window was clicked (delete_event) */
272 gboolean
273 window_delete_event_cb(GtkWidget *win, GdkEvent *event _U_, gpointer user_data _U_)
274 {
275     window_destroy(win);
276
277     /* event handled, don't do anything else */
278     return TRUE;
279 }
280
281
282 /* get the geometry of a window from window_new() */
283 void
284 window_get_geometry(GtkWidget *widget, window_geometry_t *geom)
285 {
286     gint desk_x, desk_y;
287     GdkWindowState state;
288
289     /* Try to grab our geometry.
290
291        GTK+ provides two routines to get a window's position relative
292        to the X root window.  If I understand the documentation correctly,
293        gdk_window_get_deskrelative_origin applies mainly to Enlightenment
294        and gdk_window_get_root_origin applies for all other WMs.
295
296        The code below tries both routines, and picks the one that returns
297        the upper-left-most coordinates.
298
299        More info at:
300
301         http://mail.gnome.org/archives/gtk-devel-list/2001-March/msg00289.html
302         http://www.gtk.org/faq/#AEN606
303      */
304
305     gdk_window_get_root_origin(widget->window,
306         &geom->x,
307         &geom->y);
308     if (gdk_window_get_deskrelative_origin(widget->window,
309                                 &desk_x, &desk_y)) {
310         if (desk_x <= geom->x &&
311             desk_y <= geom->y)
312         {
313             geom->x = desk_x;
314             geom->y = desk_y;
315         }
316     }
317
318     /* XXX - Is this the "approved" method? */
319     gdk_window_get_size(widget->window,
320         &geom->width,
321         &geom->height);
322
323     state = gdk_window_get_state(widget->window);
324     geom->maximized = (state == GDK_WINDOW_STATE_MAXIMIZED);
325 }
326
327
328 /* set the geometry of a window from window_new() */
329 void
330 window_set_geometry(GtkWidget *widget, window_geometry_t *geom)
331 {
332     /* as we now have the geometry from the recent file, set it */
333     /* if the window was minimized, x and y are -32000 (at least on Win32) */
334     if (geom->set_pos && geom->x != -32000 && geom->y != -32000) {
335         gtk_window_move(GTK_WINDOW(widget),
336                         geom->x,
337                         geom->y);
338     }
339
340     if (geom->set_size) {
341         gtk_window_resize(GTK_WINDOW(widget),
342         /*gtk_widget_set_size_request(widget,*/
343                                 geom->width,
344                                 geom->height);
345     }
346
347     if(geom->set_maximized) {
348         if (geom->maximized) {
349             gdk_window_maximize(widget->window);
350         } else {
351             gdk_window_unmaximize(widget->window);
352         }
353     }
354 }
355
356
357 /* the geometry hashtable for all known window classes,
358  * the window name is the key, and the geometry struct is the value */
359 GHashTable *window_geom_hash = NULL;
360
361
362 /* save the window and it's current geometry into the geometry hashtable */
363 static void
364 window_geom_save(const gchar *name, window_geometry_t *geom)
365 {
366     gchar *key;
367     window_geometry_t *work;
368
369     /* init hashtable, if not already done */
370     if(!window_geom_hash) {
371         window_geom_hash = g_hash_table_new (g_str_hash, g_str_equal);
372     }
373     /* if we have an old one, remove and free it first */
374     work = g_hash_table_lookup(window_geom_hash, name);
375     if(work) {
376         g_hash_table_remove(window_geom_hash, name);
377         g_free(work->key);
378         g_free(work);
379     }
380
381     /* g_malloc and insert the new one */
382     work = g_malloc(sizeof(*geom));
383     *work = *geom;
384     key = g_strdup(name);
385     work->key = key;
386     g_hash_table_insert(window_geom_hash, key, work);
387 }
388
389
390 /* load the desired geometry for this window from the geometry hashtable */
391 static gboolean
392 window_geom_load(const gchar *name, window_geometry_t *geom)
393 {
394     window_geometry_t *p;
395
396     /* init hashtable, if not already done */
397     if(!window_geom_hash) {
398         window_geom_hash = g_hash_table_new (g_str_hash, g_str_equal);
399     }
400
401     p = g_hash_table_lookup(window_geom_hash, name);
402     if(p) {
403         *geom = *p;
404         return TRUE;
405     } else {
406         return FALSE;
407     }
408 }
409
410
411 /* read in a single key value pair from the recent file into the geometry hashtable */
412 void
413 window_geom_recent_read_pair(const char *name, const char *key, const char *value)
414 {
415     window_geometry_t geom;
416
417
418     /* find window geometry maybe already in hashtable */
419     if(!window_geom_load(name, &geom)) {
420         /* not in table, init geom with "basic" values */
421         geom.key        = NULL;    /* Will be set in window_geom_save () */
422         geom.set_pos    = FALSE;
423         geom.x          = -1;
424         geom.y          = -1;
425         geom.set_size   = FALSE;
426         geom.width      = -1;
427         geom.height     = -1;
428
429         geom.set_maximized = FALSE;/* this is valid in GTK2 only */
430         geom.maximized  = FALSE;   /* this is valid in GTK2 only */
431     }
432
433     if (strcmp(key, "x") == 0) {
434         geom.x = strtol(value, NULL, 10);
435         geom.set_pos = TRUE;
436     } else if (strcmp(key, "y") == 0) {
437         geom.y = strtol(value, NULL, 10);
438         geom.set_pos = TRUE;
439     } else if (strcmp(key, "width") == 0) {
440         geom.width = strtol(value, NULL, 10);
441         geom.set_size = TRUE;
442     } else if (strcmp(key, "height") == 0) {
443         geom.height = strtol(value, NULL, 10);
444         geom.set_size = TRUE;
445     } else if (strcmp(key, "maximized") == 0) {
446         if (g_ascii_strcasecmp(value, "true") == 0) {
447             geom.maximized = TRUE;
448         }
449         else {
450             geom.maximized = FALSE;
451         }
452         geom.set_maximized = TRUE;
453     } else {
454         /*
455          * Silently ignore the bogus key.  We shouldn't abort here,
456          * as this could be due to a corrupt recent file.
457          *
458          * XXX - should we print a message about this?
459          */
460         return;
461     }
462
463     /* save / replace geometry in hashtable */
464     window_geom_save(name, &geom);
465 }
466
467
468 /* write all geometry values of all windows from the hashtable to the recent file */
469 void
470 window_geom_recent_write_all(gpointer rf)
471 {
472     /* init hashtable, if not already done */
473     if(!window_geom_hash) {
474         window_geom_hash = g_hash_table_new (g_str_hash, g_str_equal);
475     }
476
477     g_hash_table_foreach(window_geom_hash, write_recent_geom, rf);
478 }
479
480
481 void
482 window_destroy(GtkWidget *win)
483 {
484   window_geometry_t geom;
485   const gchar *name;
486
487   /* get_geometry must be done *before* destroy is running, as the window geometry
488    * cannot be retrieved at destroy time (so don't use event "destroy" for this) */
489   /* ...and don't do this at all, if we currently have no GdkWindow (e.g. if the
490    * GtkWidget is hidden) */
491   if(!GTK_WIDGET_NO_WINDOW(win) && GTK_WIDGET_VISIBLE(win)) {
492       window_get_geometry(win, &geom);
493
494       name = g_object_get_data(G_OBJECT(win), WINDOW_GEOM_KEY);
495       if(name) {
496         window_geom_save(name, &geom);
497         g_free((gpointer)name);
498       }
499   }
500
501   gtk_widget_destroy(win);
502 }
503
504
505 /* convert an xpm to a GtkWidget, using the window settings from it's parent */
506 /* (be sure that the parent window is already being displayed) */
507 GtkWidget *xpm_to_widget_from_parent(GtkWidget *parent, const char ** xpm) {
508     GdkPixbuf * pixbuf;
509     GdkPixmap * pixmap;
510     GdkBitmap * bitmap;
511
512
513     pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
514     gdk_pixbuf_render_pixmap_and_mask_for_colormap (pixbuf, gtk_widget_get_colormap(parent), &pixmap, &bitmap, 128);
515
516     return gtk_image_new_from_pixmap (pixmap, bitmap);
517 }
518
519
520 /* convert an xpm to a GtkWidget, using the top_level window settings */
521 /* (be sure that the top_level window is already being displayed) */
522 GtkWidget *xpm_to_widget(const char ** xpm) {
523     return xpm_to_widget_from_parent(top_level, xpm);
524 }
525
526 /* Convert an pixbuf data to a GtkWidget */
527 /* Data should be created with "gdk-pixbuf-csource --raw" */
528 GtkWidget *pixbuf_to_widget(const char * pb_data) {
529     GdkPixbuf *pixbuf;
530     
531     pixbuf = gdk_pixbuf_new_from_inline (-1, pb_data, FALSE, NULL);
532     return gtk_image_new_from_pixbuf(pixbuf);
533 }
534
535 /*
536  * Key to attach the "un-decorated" title to the window, so that if the
537  * user-specified decoration changes, we can correctly update the
538  * window title.
539  */
540 #define MAIN_WINDOW_NAME_KEY    "main_window_name"
541
542 /* Set the name of the top-level window and its icon to the specified
543    string. */
544 void
545 set_main_window_name(const gchar *window_name)
546 {
547   gchar *old_window_name;
548
549   /* Attach the new un-decorated window name to the window. */
550   old_window_name = g_object_get_data(G_OBJECT(top_level), MAIN_WINDOW_NAME_KEY);
551   g_free(old_window_name);
552   g_object_set_data(G_OBJECT(top_level), MAIN_WINDOW_NAME_KEY, g_strdup(window_name));
553
554   update_main_window_name();
555 }
556
557 /* Update the name of the main window if the user-specified decoration
558    changed. */
559 void
560 update_main_window_name(void)
561 {
562   gchar *window_name;
563   gchar *title;
564
565   window_name = g_object_get_data(G_OBJECT(top_level), MAIN_WINDOW_NAME_KEY);
566   if (window_name != NULL) {
567     /* use user-defined window title if preference is set */
568     title = create_user_window_title(window_name);
569     gtk_window_set_title(GTK_WINDOW(top_level), title);
570     gdk_window_set_icon_name(top_level->window, title);
571     g_free(title);
572   }
573 }
574
575 /* update the main window */
576 void main_window_update(void)
577 {
578   while (gtk_events_pending()) gtk_main_iteration();
579 }
580
581 /* exit the main window */
582 void main_window_exit(void)
583 {
584   exit(0);
585 }
586
587 #ifdef HAVE_LIBPCAP
588
589 /* quit a nested main window */
590 void main_window_nested_quit(void)
591 {
592   if (gtk_main_level() > 0)
593     gtk_main_quit();
594 }
595
596 /* quit the main window */
597 void main_window_quit(void)
598 {
599   gtk_main_quit();
600 }
601
602
603
604 typedef struct pipe_input_tag {
605     gint                source;
606     gpointer            user_data;
607     int                 *child_process;
608     pipe_input_cb_t     input_cb;
609     guint               pipe_input_id;
610 #ifdef _WIN32
611 #else
612     GIOChannel          *channel;
613 #endif    
614 } pipe_input_t;
615
616
617 #ifdef _WIN32
618 /* The timer has expired, see if there's stuff to read from the pipe,
619    if so, do the callback */
620 static gboolean
621 pipe_timer_cb(gpointer data)
622 {
623   HANDLE handle;
624   DWORD avail = 0;
625   gboolean result, result1;
626   DWORD childstatus;
627   pipe_input_t *pipe_input = data;
628   gint iterations = 0;
629
630
631   /* try to read data from the pipe only 5 times, to avoid blocking */
632   while(iterations < 5) {
633           /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: new iteration");*/
634
635           /* Oddly enough although Named pipes don't work on win9x,
636                  PeekNamedPipe does !!! */
637           handle = (HANDLE) _get_osfhandle (pipe_input->source);
638           result = PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL);
639
640           /* Get the child process exit status */
641           result1 = GetExitCodeProcess((HANDLE)*(pipe_input->child_process),
642                                                                    &childstatus);
643
644           /* If the Peek returned an error, or there are bytes to be read
645                  or the childwatcher thread has terminated then call the normal
646                  callback */
647           if (!result || avail > 0 || childstatus != STILL_ACTIVE) {
648
649                 /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: data avail");*/
650
651                 if(pipe_input->pipe_input_id != 0) {
652                   /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: stop timer");*/
653                   /* avoid reentrancy problems and stack overflow */
654                   g_source_remove(pipe_input->pipe_input_id);
655                   pipe_input->pipe_input_id = 0;
656                 }
657
658                 /* And call the real handler */
659                 if (!pipe_input->input_cb(pipe_input->source, pipe_input->user_data)) {
660                         g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: input pipe closed, iterations: %u", iterations);
661                         /* pipe closed, return false so that the old timer is not run again */
662                         return FALSE;
663                 }
664           }
665           else {
666                 /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: no data avail");*/
667                 /* No data, stop now */
668                 break;
669           }
670
671           iterations++;
672   }
673
674   if(pipe_input->pipe_input_id == 0) {
675         /* restore pipe handler */
676         pipe_input->pipe_input_id = g_timeout_add(200, pipe_timer_cb, data);
677         /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: finished with iterations: %u, new timer", iterations);*/
678
679         /* Return false so that the old timer is not run again */
680         return FALSE;
681   } else {
682         /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_timer_cb: finished with iterations: %u, old timer", iterations);*/
683
684         /* we didn't stopped the old timer, so let it run */
685         return TRUE;
686   }
687 }
688
689 #else /* _WIN32 */
690
691 /* There's stuff to read from the sync pipe, meaning the child has sent
692    us a message, or the sync pipe has closed, meaning the child has
693    closed it (perhaps because it exited). */
694 static gboolean
695 pipe_input_cb(GIOChannel *source _U_, GIOCondition condition _U_,
696                gpointer data)
697 {
698   pipe_input_t *pipe_input = data;
699
700
701   /* avoid reentrancy problems and stack overflow */
702   g_source_remove(pipe_input->pipe_input_id);
703
704   if (pipe_input->input_cb(pipe_input->source, pipe_input->user_data)) {
705     /* restore pipe handler */
706     pipe_input->pipe_input_id = g_io_add_watch_full (pipe_input->channel,
707                                      G_PRIORITY_HIGH,
708                                      G_IO_IN|G_IO_ERR|G_IO_HUP,
709                                      pipe_input_cb,
710                                      pipe_input,
711                                      NULL);
712   }
713   return TRUE;
714 }
715 #endif
716
717 void pipe_input_set_handler(gint source, gpointer user_data, int *child_process, pipe_input_cb_t input_cb)
718 {
719     static pipe_input_t pipe_input;
720
721     pipe_input.source        = source;
722     pipe_input.child_process = child_process;
723     pipe_input.user_data     = user_data;
724     pipe_input.input_cb      = input_cb;
725
726 #ifdef _WIN32
727     /* Tricky to use pipes in win9x, as no concept of wait.  NT can
728        do this but that doesn't cover all win32 platforms.  GTK can do
729        this but doesn't seem to work over processes.  Attempt to do
730        something similar here, start a timer and check for data on every
731        timeout. */
732         /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
733     pipe_input.pipe_input_id = g_timeout_add(200, pipe_timer_cb, &pipe_input);
734 #else
735     pipe_input.channel = g_io_channel_unix_new(source);
736     g_io_channel_set_encoding(pipe_input.channel, NULL, NULL);
737     pipe_input.pipe_input_id = g_io_add_watch_full(pipe_input.channel,
738                                       G_PRIORITY_HIGH,
739                                       G_IO_IN|G_IO_ERR|G_IO_HUP,
740                                       pipe_input_cb,
741                                       &pipe_input,
742                                       NULL);
743 #endif
744 }
745
746
747 #endif /* HAVE_LIBPCAP */
748
749 /* Given a pointer to a GtkWidget for a top-level window, raise it and
750    de-iconify it.  This routine is used if the user has done something to
751    ask that a window of a certain type be popped up when there can be only
752    one such window and such a window has already been popped up - we
753    pop up the existing one rather than creating a new one.
754
755    XXX - we should request that it be given the input focus, too.  Alas,
756    GDK has nothing to do that, e.g. by calling "XSetInputFocus()" in a
757    window in X.  Besides, using "XSetInputFocus()" doesn't work anyway,
758    apparently due to the way GTK+/GDK manages the input focus.
759
760    The X Desktop Group's Window Manager Standard specifies, in the section
761    on Root Window Properties, an _NET_ACTIVE_WINDOW client message that
762    can be sent to the root window, containing the window ID of the
763    window to activate; I infer that this might be the way to give the
764    window the input focus - I assume that means it's also de-iconified,
765    but I wouldn't assume it'd raise it.
766
767    XXX - will this do the right thing on window systems other than X? */
768 void
769 reactivate_window(GtkWidget *win)
770 {
771   gdk_window_show(win->window);
772   gdk_window_raise(win->window);
773 }
774
775 /* List of all GtkScrolledWindows, so we can globally set the scrollbar
776    placement of all of them. */
777 static GList *scrolled_windows;
778
779 static void setup_scrolled_window(GtkWidget *scrollw);
780 static void forget_scrolled_window(GtkWidget *scrollw, gpointer data);
781 static void set_scrollbar_placement_scrollw(GtkWidget *scrollw);
782
783 /* Create a GtkScrolledWindow, set its scrollbar placement appropriately,
784    and remember it. */
785 GtkWidget *
786 scrolled_window_new(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment)
787 {
788   GtkWidget *scrollw;
789
790   scrollw = gtk_scrolled_window_new(hadjustment, vadjustment);
791   setup_scrolled_window(scrollw);
792   return scrollw;
793 }
794
795 /* Set a GtkScrolledWindow's scrollbar placement and add it to the list
796    of GtkScrolledWindows. */
797 static void
798 setup_scrolled_window(GtkWidget *scrollw)
799 {
800   set_scrollbar_placement_scrollw(scrollw);
801
802   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollw),
803                                    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
804
805   scrolled_windows = g_list_append(scrolled_windows, scrollw);
806
807   /* Catch the "destroy" event on the widget, so that we remove it from
808      the list when it's destroyed. */
809   g_signal_connect(scrollw, "destroy", G_CALLBACK(forget_scrolled_window), NULL);
810 }
811
812 /* Remove a GtkScrolledWindow from the list of GtkScrolledWindows. */
813 static void
814 forget_scrolled_window(GtkWidget *scrollw, gpointer data _U_)
815 {
816   scrolled_windows = g_list_remove(scrolled_windows, scrollw);
817 }
818
819 /* Set the scrollbar placement of a GtkScrolledWindow based upon user
820    preference. */
821 static void
822 set_scrollbar_placement_scrollw(GtkWidget *scrollw)
823 {
824   if (prefs.gui_scrollbar_on_right) {
825     gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(scrollw),
826                                       GTK_CORNER_TOP_LEFT);
827   } else {
828     gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(scrollw),
829                                       GTK_CORNER_TOP_RIGHT);
830   }
831 }
832
833 static void
834 set_scrollbar_placement_cb(gpointer data, gpointer user_data _U_)
835 {
836   set_scrollbar_placement_scrollw((GtkWidget *)data);
837 }
838
839 /* Set the scrollbar placement of all GtkScrolledWindows based on
840    user preference. */
841 void
842 set_scrollbar_placement_all(void)
843 {
844   g_list_foreach(scrolled_windows, set_scrollbar_placement_cb, NULL);
845 }
846
847 /* List of all CTrees/TreeViews, so we can globally set the line and
848  * expander style of all of them. */
849 static GList *trees;
850
851 static void setup_tree(GtkWidget *tree);
852 static void forget_tree(GtkWidget *tree, gpointer data);
853 static void set_tree_styles(GtkWidget *tree);
854 static int tree_view_key_pressed_cb(GtkWidget *tree, GdkEventKey *event, gpointer user_data _U_);
855
856 /* Create a Tree, give it the right styles, and remember it. */
857 GtkWidget *
858 tree_view_new(GtkTreeModel *model)
859 {
860   GtkWidget *tree;
861
862   tree = gtk_tree_view_new_with_model(model);
863   setup_tree(tree);
864   return tree;
865 }
866
867 /* Set a Tree's styles and add it to the list of Trees. */
868 static void
869 setup_tree(GtkWidget *tree)
870 {
871   set_tree_styles(tree);
872
873   trees = g_list_append(trees, tree);
874
875   /* Catch the "destroy" event on the widget, so that we remove it from
876      the list when it's destroyed. */
877   g_signal_connect(tree, "destroy", G_CALLBACK(forget_tree), NULL);
878   g_signal_connect(tree, "key-press-event", G_CALLBACK(tree_view_key_pressed_cb), NULL );
879 }
880
881 /* Remove a Tree from the list of Trees. */
882 static void
883 forget_tree(GtkWidget *tree, gpointer data _U_)
884 {
885   trees = g_list_remove(trees, tree);
886 }
887
888 /* Set the styles of a Tree based upon user preferences. */
889 static void
890 set_tree_styles(GtkWidget *tree)
891 {
892   g_assert(prefs.gui_altern_colors >= 0 && prefs.gui_altern_colors <= 1);
893   gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree),
894                                prefs.gui_altern_colors);
895 }
896
897 static void
898 set_tree_styles_cb(gpointer data, gpointer user_data _U_)
899 {
900   set_tree_styles((GtkWidget *)data);
901 }
902
903 /* Set the styles of all Trees based upon style values. */
904 void
905 set_tree_styles_all(void)
906 {
907   g_list_foreach(trees, set_tree_styles_cb, NULL);
908 }
909
910 /* Move the currently-selected item in a list store up or down one position. */
911 gboolean
912 tree_view_list_store_move_selection(GtkTreeView *tree, gboolean move_up)
913 {
914   GtkTreeIter       from, to;
915   GtkTreeModel     *model;
916   GtkTreeSelection *sel;
917   GtkTreePath      *path_from, *path_to;
918
919   sel = gtk_tree_view_get_selection(tree);
920   if (! gtk_tree_selection_get_selected(sel, &model, &from)) {
921     return FALSE;
922   }
923
924   path_from = gtk_tree_model_get_path(model, &from);
925   if (!path_from) {
926     return FALSE;
927   }
928
929   path_to = gtk_tree_path_copy(path_from);
930   /* XXX - Why does one return void and the other return a gboolean? */
931   if (move_up) {
932     gtk_tree_path_prev(path_to);
933   } else {
934     gtk_tree_path_next(path_to);
935   }
936
937   if (gtk_tree_path_compare(path_from, path_to) == 0) {
938     gtk_tree_path_free(path_from);
939     gtk_tree_path_free(path_to);
940     return FALSE;
941   }
942
943   gtk_tree_model_get_iter(model, &to, path_to);
944   gtk_list_store_swap(GTK_LIST_STORE(model), &from, &to);
945   gtk_tree_path_free(path_from);
946   gtk_tree_path_free(path_to);
947   return TRUE;
948 }
949
950 /* Find the selected row number in a list store. */
951 gint
952 tree_view_list_store_get_selected_row(GtkTreeView *tree) {
953   GtkTreeIter       iter;
954   GtkTreeModel     *model;
955   GtkTreeSelection *sel;
956   GtkTreePath      *path;
957   gchar            *path_str;
958   gint              row;
959
960   sel = gtk_tree_view_get_selection(tree);
961   if (! gtk_tree_selection_get_selected(sel, &model, &iter)) {
962     return -1;
963   }
964
965   path = gtk_tree_model_get_path(model, &iter);
966   if (!path) {
967     return FALSE;
968   }
969   
970   path_str = gtk_tree_path_to_string(path);
971   gtk_tree_path_free(path);
972   
973   row = (gint) strtol(path_str, NULL, 10);
974   g_free(path_str);
975   
976   return row;
977 }
978
979 /* append a row to the simple list */
980 /* use it like: simple_list_append(list, 0, "first", 1, "second", -1) */
981 void
982 simple_list_append(GtkWidget *list, ...)
983 {
984     va_list ap;
985
986     GtkTreeIter iter;
987     GtkListStore *store;
988
989     va_start(ap, list);
990     store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
991     gtk_list_store_append(store, &iter);
992     gtk_list_store_set_valist(store, &iter, ap);
993     va_end(ap);
994 }
995
996 /* create a simple list widget */
997 GtkWidget *
998 simple_list_new(gint cols, const gchar **titles) {
999     GtkWidget *plugins_list;
1000     int i;
1001     GtkListStore *store;
1002     GtkCellRenderer *renderer;
1003     GtkTreeViewColumn *column;
1004
1005
1006     g_assert(cols <= 10);
1007     store = gtk_list_store_new(cols,
1008         G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1009         G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
1010     plugins_list = tree_view_new(GTK_TREE_MODEL(store));
1011     g_object_unref(G_OBJECT(store));
1012     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(plugins_list), (titles != NULL));
1013     for(i=0; i<cols; i++) {
1014         renderer = gtk_cell_renderer_text_new();
1015         column = gtk_tree_view_column_new_with_attributes(titles ? titles[i] : "", renderer,
1016                                                           "text", i, NULL);
1017         gtk_tree_view_column_set_sort_column_id(column, i);
1018         gtk_tree_view_append_column(GTK_TREE_VIEW(plugins_list), column);
1019     }
1020
1021     return plugins_list;
1022 }
1023
1024 void render_as_url(GtkCellRenderer *cell)
1025 {
1026     g_object_set(cell, "foreground", "blue", NULL);
1027     g_object_set(cell, "foreground-set", TRUE, NULL);
1028
1029     g_object_set(cell, "underline", PANGO_UNDERLINE_SINGLE, NULL);
1030     g_object_set(cell, "underline-set", TRUE, NULL);
1031 }
1032
1033 void simple_list_url_col(GtkWidget *list, gint col)
1034 {
1035   GtkTreeViewColumn *ul_column;
1036   GList             *renderers_list;
1037   GtkCellRenderer   *ul_renderer;
1038
1039   /* make the column look like a link ... */
1040   ul_column = gtk_tree_view_get_column(GTK_TREE_VIEW(list), col);
1041
1042   renderers_list = gtk_tree_view_column_get_cell_renderers(ul_column);
1043
1044   if(renderers_list != NULL) {
1045     /* it is simple list - there should be only one renderer */
1046     ul_renderer = (GtkCellRenderer*)renderers_list->data;
1047
1048     render_as_url(ul_renderer);
1049
1050     g_list_free(renderers_list);
1051   }
1052
1053 }
1054
1055 void
1056 copy_to_clipboard(GString *str)
1057 {
1058         GtkClipboard    *cb;
1059
1060         cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);     /* Get the default clipboard */
1061         gtk_clipboard_set_text(cb, str->str, -1);            /* Copy the byte data into the clipboard */
1062 }
1063
1064
1065 typedef struct _copy_binary_t {
1066     guint8* data;
1067     int len;
1068 } copy_binary_t;
1069
1070 static
1071 copy_binary_t* create_copy_binary_t(const guint8* data, int len)
1072 {
1073     copy_binary_t* copy_data;
1074
1075     g_assert(len > 0);
1076     copy_data = g_new(copy_binary_t,1);
1077     copy_data->data = g_new(guint8,len);
1078     copy_data->len = len;
1079     memcpy(copy_data->data,data,len * sizeof(guint8));
1080     return copy_data;
1081 }
1082
1083 static void destroy_copy_binary_t(copy_binary_t* copy_data) {
1084     g_free(copy_data->data);
1085     g_free(copy_data);
1086 }
1087
1088 static
1089 void copy_binary_free_cb(GtkClipboard *clipboard _U_, gpointer user_data_or_owner)
1090 {
1091     copy_binary_t* copy_data;
1092     copy_data = user_data_or_owner;
1093     destroy_copy_binary_t(copy_data);
1094 }
1095
1096 static
1097 void copy_binary_get_cb(GtkClipboard *clipboard _U_, GtkSelectionData *selection_data, guint info _U_, gpointer user_data_or_owner)
1098 {
1099     copy_binary_t* copy_data;
1100
1101     copy_data = user_data_or_owner;
1102
1103     /* Just do a dumb set as binary data */
1104     gtk_selection_data_set(selection_data, GDK_NONE, 8, copy_data->data, copy_data->len);
1105 }
1106
1107 void copy_binary_to_clipboard(const guint8* data_p, int len)
1108 {
1109     static GtkTargetEntry target_entry[] = {
1110          {"application/octet_stream", 0, 0}}; /* XXX - this not understood by most applications,
1111                                              * but can be pasted into the better hex editors - is
1112                                              * there something better that we can do?
1113                                              */
1114
1115     GtkClipboard    *cb;
1116     copy_binary_t* copy_data;
1117     gboolean ret;
1118
1119     if(len <= 0) {
1120         return; /* XXX would it be better to clear the clipboard? */
1121     }
1122     cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);     /* Get the default clipboard */
1123     copy_data = create_copy_binary_t(data_p,len);
1124
1125     ret = gtk_clipboard_set_with_data(cb,target_entry,1,
1126         copy_binary_get_cb, copy_binary_free_cb,copy_data);
1127
1128     if(!ret) {
1129         destroy_copy_binary_t(copy_data);
1130     }
1131 }
1132
1133 /*
1134  * Create a new window title string with user-defined title preference.
1135  * (Or ignore it if unspecified).
1136  */
1137 gchar *
1138 create_user_window_title(const gchar *caption)
1139 {
1140         /* fail-safe */
1141         if (caption == NULL)
1142                 return g_strdup("");
1143
1144         /* no user-defined title specified */
1145         if ((prefs.gui_window_title == NULL) || (*prefs.gui_window_title == '\0'))
1146                 return g_strdup(caption);
1147
1148         return g_strdup_printf("%s %s", prefs.gui_window_title, caption);
1149 }
1150
1151 /* XXX move toggle_tree over from proto_draw.c to handle GTK+ 1 */
1152 /*
1153  * This callback is invoked when keyboard focus is within either 
1154  * the packetlist view or the detail view.  The keystrokes processed 
1155  * within this callback are attempting to modify the detail view.  
1156  * Within the detail view we special case the Left Arrow, Backspace 
1157  * and Enter keys depending on the state of the expander (if any)
1158  * for the item in focus.
1159  *
1160  * Returning FALSE allows processing of the original key_press_event 
1161  * by other callbacks.  Left/Right scrolling of the packetlist
1162  * view and expanding/collapsing of the detail view lists is
1163  * handled by the default GtkTreeView key-press-event call back.
1164  *
1165  * XXX - Would an improved version of this callback test to see which
1166  * of the two GtkTreeView lists has focus?  Left/Right scrolling of 
1167  * the packetlist is currently not optimal.  It will take several 
1168  * right or left keypress events before the packetlist responds.  
1169  * The problem appears to be that the focus is on a particular cell
1170  * within the highlighted row cell (like a spreadsheet).  Scrolling
1171  * of the view  right or left will not occur until the focus is 
1172  * moved to a cell off the left or right edge of the packet list 
1173  * view.  Also TAB/SHIFT-TAB events can move keyboard focus to 
1174  * the packetlist header where there is currently visual hint
1175  * a header cell has focus.
1176  */
1177 static int
1178 tree_view_key_pressed_cb(GtkWidget *tree, GdkEventKey *event, gpointer user_data _U_)
1179 {
1180     GtkTreeSelection* selection;
1181     GtkTreeIter iter;
1182     GtkTreeIter parent;
1183     GtkTreeModel* model;
1184     GtkTreePath* path;
1185     gboolean    expanded;
1186     int rc = FALSE;
1187
1188     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
1189     if(!selection) {
1190         return FALSE;
1191     }
1192
1193     if(!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1194         return FALSE;
1195     }
1196
1197     path = gtk_tree_model_get_path(model, &iter);
1198     if(!path) {
1199         return FALSE;
1200     }
1201     expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(tree), path);
1202
1203     switch (event->keyval) {
1204         case GDK_Left:
1205             if(expanded) {
1206                 /* subtree is expanded, collapse it by letting default callback handle it. */
1207                 rc = FALSE;
1208                 break; 
1209             }
1210             /* No break - fall through to jumping to the parent */
1211         case GDK_BackSpace:
1212             if (!expanded) {
1213                 /* subtree is already collapsed, jump to parent node */
1214                 if(! gtk_tree_model_iter_parent(model, &parent, &iter)) {
1215                     rc = FALSE;
1216                     break; 
1217                 }
1218                 gtk_tree_path_free(path);
1219                 path = gtk_tree_model_get_path(model, &parent);
1220                 if(!path) {
1221                     return FALSE;
1222                 }
1223                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree), path,
1224                                                  NULL /* focus_column */,
1225                                                  FALSE /* !start_editing */);
1226                 rc = TRUE;
1227                 break; 
1228             }
1229             break;
1230         case GDK_Return:
1231         case GDK_KP_Enter:
1232             /* Reverse the current state. */
1233             if (expanded)
1234                 gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), path);
1235             else
1236                 gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), path, FALSE /* !open_all */);
1237             gtk_tree_path_free(path);
1238             rc = TRUE;
1239             break; 
1240     }
1241
1242     if(path) {
1243         gtk_tree_path_free(path);
1244     }
1245     return rc;
1246 }
1247
1248 void
1249 switch_to_fixed_col(GtkTreeView *view)
1250 {
1251     gint size;
1252     GtkTreeViewColumn *column;
1253     GList           *columns, *list;
1254
1255     columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
1256     list = columns;
1257     while(columns) {
1258         column = columns->data;
1259         size = gtk_tree_view_column_get_width (column);
1260         gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_FIXED);
1261         if (size > gtk_tree_view_column_get_fixed_width(column))
1262             gtk_tree_view_column_set_fixed_width(column, size);
1263         columns = g_list_next(columns);
1264     }
1265     g_list_free(list);
1266     
1267 #if GTK_CHECK_VERSION(2,6,0)
1268     gtk_tree_view_set_fixed_height_mode(view, TRUE);
1269 #endif
1270 }
1271
1272 gint 
1273 get_default_col_size(GtkWidget *view, const gchar *str)
1274 {
1275     PangoLayout *layout;
1276     gint col_width;
1277
1278     layout = gtk_widget_create_pango_layout(view, str);
1279     pango_layout_get_pixel_size(layout, 
1280                                 &col_width, /* width */
1281                                 NULL); /* height */
1282     g_object_unref(G_OBJECT(layout));
1283     /* Add a single character's width to get some spacing between columns */
1284     return col_width + (pango_font_description_get_size(user_font_get_regular()) / PANGO_SCALE);
1285 }
1286
1287
1288 /* 
1289  * This function can be called from gtk_tree_view_column_set_cell_data_func()
1290  * the user data must be the colum number.
1291  * Present floats with two decimals 
1292  */
1293 void
1294 float_data_func (GtkTreeViewColumn *column _U_,
1295                  GtkCellRenderer   *renderer,
1296                  GtkTreeModel      *model,
1297                  GtkTreeIter       *iter,
1298                  gpointer           user_data)
1299 {
1300     gfloat  float_val;
1301     gchar   buf[20];
1302     char *savelocale;
1303
1304     /* the col to get data from is in userdata */
1305     gint float_col = GPOINTER_TO_INT(user_data);
1306
1307     gtk_tree_model_get(model, iter, float_col, &float_val, -1);
1308
1309     /* save the current locale */
1310     savelocale = setlocale(LC_NUMERIC, NULL);
1311     /* switch to "C" locale to avoid problems with localized decimal separators
1312      * in g_snprintf("%f") functions
1313      */
1314     setlocale(LC_NUMERIC, "C");
1315
1316     g_snprintf(buf, sizeof(buf), "%.2f", float_val);
1317     /* restore previous locale setting */
1318     setlocale(LC_NUMERIC, savelocale);
1319
1320     g_object_set(renderer, "text", buf, NULL);
1321 }
1322
1323 /* 
1324  * This function can be called from gtk_tree_view_column_set_cell_data_func()
1325  * the user data must be the colum number.
1326  * Present value as hexadecimal. 
1327  */
1328 void
1329 present_as_hex_func (GtkTreeViewColumn *column _U_,
1330                      GtkCellRenderer   *renderer,
1331                      GtkTreeModel      *model,
1332                      GtkTreeIter       *iter,
1333                      gpointer           user_data)
1334 {
1335     guint  val;
1336     gchar   buf[35];
1337
1338     /* the col to get data from is in userdata */
1339     gint col = GPOINTER_TO_INT(user_data);
1340
1341     gtk_tree_model_get(model, iter, col, &val, -1);
1342
1343     g_snprintf(buf, sizeof(buf), "0x%02x", val);
1344
1345     g_object_set(renderer, "text", buf, NULL);
1346 }
1347
1348 void
1349 u64_data_func (GtkTreeViewColumn *column _U_,
1350                GtkCellRenderer   *renderer,
1351                GtkTreeModel      *model,
1352                GtkTreeIter       *iter,
1353                gpointer           user_data)
1354 {
1355     guint64 val;
1356     int i = 0;
1357     gchar *bp;
1358     gchar   buf[35];
1359
1360     /* the col to get data from is in userdata */
1361     gint col = GPOINTER_TO_INT(user_data);
1362
1363     gtk_tree_model_get(model, iter, col, &val, -1);
1364
1365     bp = &buf[34];
1366     *bp = 0;
1367     do {
1368         *--bp = (gchar)(val % 10) +'0';
1369         if (!(++i % 3)) {
1370             *--bp = ' ';
1371         }
1372     } while ((val /= 10) != 0 && bp > buf);
1373     g_object_set(renderer, "text", bp, NULL);
1374 }
1375
1376 /* 
1377  * This function can be called from gtk_tree_view_column_set_cell_data_func()
1378  * the user data must be the colum number.
1379  * Renders the const static string whos pointer is stored 
1380  */
1381 void
1382 str_ptr_data_func (GtkTreeViewColumn *column _U_,
1383                    GtkCellRenderer   *renderer,
1384                    GtkTreeModel      *model,
1385                    GtkTreeIter       *iter,
1386                    gpointer           user_data)
1387 {       
1388     const gchar *str = NULL;
1389
1390     /* The col to get data from is in userdata */
1391     gint data_column = GPOINTER_TO_INT(user_data);
1392
1393     gtk_tree_model_get(model, iter, data_column, &str, -1);
1394     /* XXX should we check that str is non NULL and print a warning or do assert? */
1395
1396     g_object_set(renderer, "text", str, NULL);
1397 }
1398
1399 gint
1400 str_ptr_sort_func(GtkTreeModel *model,
1401                   GtkTreeIter *a,
1402                   GtkTreeIter *b,
1403                   gpointer user_data)
1404 {
1405     const gchar *str_a = NULL;
1406     const gchar *str_b = NULL;
1407     gint ret = 0;
1408
1409     /* The col to get data from is in userdata */
1410     gint data_column = GPOINTER_TO_INT(user_data);
1411
1412     gtk_tree_model_get(model, a, data_column, &str_a, -1);
1413     gtk_tree_model_get(model, b, data_column, &str_b, -1);
1414
1415     if (str_a == str_b) {
1416         /* it's worth testing because a lot of rows point to the same data */
1417         return 0;
1418     } 
1419     else if (str_a == NULL || str_b == NULL) {
1420         ret = (str_a == NULL) ? -1 : 1;
1421     } 
1422     else {
1423         ret = g_ascii_strcasecmp(str_a,str_b);
1424     }
1425     return ret;
1426 }
1427
1428 /**
1429  * ws_combo_box_new_text_and_pointer:
1430  *
1431  * Convenience function which constructs a new "text and pointer" combo box, which
1432  * is a #GtkComboBox just displaying strings and storing a pointer associated with 
1433  * each combo_box entry; The pointer can be retrieved when an entry is selected. 
1434  * If you use this function to create a text_and_pointer combo_box,
1435  * you should only manipulate its data source with the
1436  * following convenience function: ws_combo_box_append_text_and_pointer().
1437  *
1438  * @return A pointer to a new text_and_pointer combo_box.
1439  */
1440 GtkWidget *
1441 ws_combo_box_new_text_and_pointer(void)
1442 {
1443     GtkWidget       *combo_box;
1444     GtkCellRenderer *cell;
1445     GtkListStore    *store;
1446
1447     store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
1448     combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL (store));
1449     g_object_unref(store);
1450
1451     cell = gtk_cell_renderer_text_new();
1452     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), cell, TRUE);
1453     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), cell,
1454                                    "text", 0,
1455                                    NULL);
1456
1457     return combo_box;
1458 }
1459
1460 /**
1461  * ws_combo_box_clear_text_and_pointer:
1462  * @param combo_box A #GtkComboBox constructed using ws_combo_box_new_text_and_pointer()
1463  *
1464  * Clears all the text_and_pointer entries in the text_and_pointer combo_box.
1465  * Note: A "changed" signal will be emitted after the clear if there was 
1466  * an active (selected) entry before the clear.
1467  * You should use this function only with combo boxes constructed with
1468  * ws_combo_box_new_text_and_pointer().
1469  */
1470 void ws_combo_box_clear_text_and_pointer(GtkComboBox *combo_box)
1471 {
1472     gtk_list_store_clear(GTK_LIST_STORE(gtk_combo_box_get_model(combo_box)));
1473 }
1474
1475 /**
1476  * ws_combo_box_append_text_and_pointer:
1477  * @param combo_box A #GtkComboBox constructed using ws_combo_box_new_text_and_pointer()
1478  * @param text A string to be displayed as an entry in the dropdown list of the combo_box
1479  * @param ptr  A pointer to be associated with this entry of the combo_box
1480  *
1481  * Appends text and ptr to the list of strings and pointers stored in combo_box. Note that
1482  * you can only use this function with combo boxes constructed with
1483  * ws_combo_box_new_text_and_pointer().
1484  */
1485 void
1486 ws_combo_box_append_text_and_pointer (GtkComboBox   *combo_box,
1487                                       const gchar   *text,
1488                                       const gpointer ptr)
1489 {
1490     GtkTreeIter   iter;
1491     GtkListStore *store;
1492
1493     store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
1494
1495     gtk_list_store_append(store, &iter);
1496     gtk_list_store_set(store, &iter, 0, text, 1, ptr, -1);
1497 }
1498
1499 /**
1500  * ws_combo_box_get_active_pointer:
1501  * @param combo_box A #GtkComboBox constructed using ws_combo_box_new_text_and_pointer()
1502  * @param ptr  A pointer to a location in which to store the pointer associated with the active entry
1503  * @return TRUE if an entry is selected (i.e: an active entry exists); FALSE otherwise
1504  *
1505  * You can only use this function with combo boxes constructed with
1506  * ws_combo_box_new_text_and_pointer().
1507  */
1508 gboolean
1509 ws_combo_box_get_active_pointer(GtkComboBox *combo_box, gpointer *ptr)
1510 {
1511     GtkListStore *store;
1512     GtkTreeIter iter;
1513
1514     *ptr = NULL;
1515
1516     if (gtk_combo_box_get_active_iter(combo_box, &iter)) {
1517         store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
1518         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 
1519                            1, ptr, -1);
1520         return TRUE;
1521     }
1522     return FALSE;
1523 }
1524
1525 /**
1526  * ws_combo_box_get_active:
1527  * @param combo_box A #GtkComboBox constructed using ws_combo_box_new_text_and_pointer()
1528  * @return Index of the active entry; -1 if no entry is selected;
1529  */
1530 gint
1531 ws_combo_box_get_active(GtkComboBox *combo_box) 
1532 {
1533     return gtk_combo_box_get_active(combo_box);
1534 }
1535
1536 /**
1537  * ws_combo_box_set_active:
1538  * @param combo_box A #GtkComboBox constructed using ws_combo_box_new_text_and_pointer()
1539  * @param Index of the entry which is to be set as active (ie: selected)
1540  */
1541 void
1542 ws_combo_box_set_active(GtkComboBox *combo_box, gint idx)
1543 {
1544     gtk_combo_box_set_active(combo_box, idx);
1545 }
1546
1547