Logo Search packages:      
Sourcecode: manedit version File versions  Download package

fb.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <fnmatch.h>
# if defined(__SOLARIS__)
#  include <sys/mnttab.h>
#  include <sys/vfstab.h>
# elif defined(__FreeBSD__)
/* #  include <mntent.h> */
# else
#  include <mntent.h>
# endif
#endif
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "../include/string.h"
#include "../include/disk.h"

#include "guiutils.h"
#include "cdialog.h"
#include "pulist.h"
#include "fprompt.h"
#include "fb.h"
#include "config.h"


#include "images/icon_file_20x20.xpm"
#include "images/icon_file_hidden_20x20.xpm"
#include "images/icon_folder_closed_20x20.xpm"
#include "images/icon_folder_opened_20x20.xpm"
#include "images/icon_folder_parent_20x20.xpm"
#include "images/icon_folder_noaccess_20x20.xpm"
#include "images/icon_folder_home_20x20.xpm"
#include "images/icon_folder_hidden_20x20.xpm"
#include "images/icon_link2_20x20.xpm"
#include "images/icon_pipe_20x20.xpm"
#include "images/icon_socket_20x20.xpm"
#include "images/icon_device_block_20x20.xpm"
#include "images/icon_device_character_20x20.xpm"
#include "images/icon_executable_20x20.xpm"
#include "images/icon_drive_fixed_20x20.xpm"
#include "images/icon_drive_root_20x20.xpm"

#include "images/icon_fb_list_standard_20x20.xpm"
#include "images/icon_fb_list_vertical_20x20.xpm"
#include "images/icon_fb_list_vertical_details_20x20.xpm"
#include "images/icon_rename_20x20.xpm"
#include "images/icon_chmod_20x20.xpm"
#include "images/icon_reload_20x20.xpm"
#include "images/icon_select_20x20.xpm"
#include "images/icon_ok_20x20.xpm"
#include "images/icon_cancel_20x20.xpm"

#include "images/icon_trash_32x32.xpm"


typedef struct _FileBrowserIcon           FileBrowserIcon;
#define FILE_BROWSER_ICON(p)        ((FileBrowserIcon *)(p))
typedef struct _FileBrowserObject   FileBrowserObject;
#define FILE_BROWSER_OBJECT(p)            ((FileBrowserObject *)(p))
typedef struct _FileBrowserColumn   FileBrowserColumn;
#define FILE_BROWSER_COLUMN(p)            ((FileBrowserColumn *)(p))
typedef struct _FileBrowser         FileBrowser;
#define FILE_BROWSER(p)             ((FileBrowser *)(p))


/*
 *    List Formats:
 */
typedef enum {
      FB_LIST_FORMAT_STANDARD,            /* Horizontal */
      FB_LIST_FORMAT_VERTICAL,            /* Vertical */
      FB_LIST_FORMAT_VERTICAL_DETAILS           /* Vertical with details */
} FileBrowserListFormat;


/*
 *    Icon Index Values:
 *
 *    The values represent the index of each icon defined in
 *    FB_ICON_DATA_LIST.
 */
typedef enum {
      FB_ICON_FILE,                       /* 0 */
      FB_ICON_FILE_HIDDEN,
      FB_ICON_FOLDER_CLOSED,
      FB_ICON_FOLDER_OPENED,
      FB_ICON_FOLDER_PARENT,
      FB_ICON_FOLDER_NOACCESS,
      FB_ICON_FOLDER_HOME,
      FB_ICON_FOLDER_HIDDEN,
      FB_ICON_LINK,
      FB_ICON_PIPE,
      FB_ICON_SOCKET,                     /* 10 */
      FB_ICON_DEVICE_BLOCK,
      FB_ICON_DEVICE_CHARACTER,
      FB_ICON_EXECUTABLE,
      FB_ICON_DRIVE_FIXED,
      FB_ICON_DRIVE_ROOT                  /* 15 */
} FileBrowserIconNum;


/* Utilities */
static gchar **FileBrowserDNDBufParse(
      const gchar *buf, const gint len, gint *n
);
static gchar *FileBrowserDNDBufFormat(
      FileBrowserObject **object, const gint total, GList *selection,
      gint *len
);
static gboolean FileBrowserObjectNameFilter(
      const gchar *name, const gchar *full_path,
      const gchar *ext
);


/* Get Drive Paths */
static gchar **FileBrowserGetDrivePaths(gint *n);


/* Busy/Ready Setting */
static void FileBrowserSetBusy(FileBrowser *fb, const gboolean busy);


/* Show Hidden Objects Setting */
static void FileBrowserSetShowHiddenObjects(
      FileBrowser *fb, const gboolean show
);


/* List Format Setting */
static void FileBrowserSetListFormat(
      FileBrowser *fb, const FileBrowserListFormat list_format
);


/* Location */
static void FileBrowserSetLocation(FileBrowser *fb, const gchar *path);
static const gchar *FileBrowserGetLocation(FileBrowser *fb);


/* Locations Popup List */
static void FileBrowserLocationsPUListUpdate(FileBrowser *fb);


/* Icons */
static FileBrowserIcon *FileBrowserGetIcon(FileBrowser *fb, const gint i);
static FileBrowserIcon *FileBrowserIconAppend(
      FileBrowser *fb, guint8 **data, const gchar *desc
);
static gint FileBrowserMatchIconNumFromPath(
      FileBrowser *fb, const gchar *full_path, const struct stat *lstat_buf
);


/* Objects */
static FileBrowserObject *FileBrowserGetObject(FileBrowser *fb, gint i);
static void FileBrowserObjectUpdateValues(
      FileBrowser *fb, FileBrowserObject *o
);
static FileBrowserObject *FileBrowserObjectAppend(
      FileBrowser *fb, const gchar *name, const gchar *full_path,
      struct stat *lstat_buf
);


/* List */
static void FileBrowserListUpdate(FileBrowser *fb, const gchar *filter);
static void FileBrowserListUpdatePositions(FileBrowser *fb);
static void FileBrowserListObjectSetDNDIcon(FileBrowser *fb, const gint i);
static GtkVisibility FileBrowserListObjectVisibility(FileBrowser *fb, const gint i);
static void FileBrowserListMoveToObject(
      FileBrowser *fb, const gint i, const gfloat coeff
);
static gint FileBrowserListSelectCoordinates(
      FileBrowser *fb, const gint x, const gint y
);
static void FileBrowserListHeaderDraw(FileBrowser *fb);
static void FileBrowserListHeaderQueueDraw(FileBrowser *fb);
static void FileBrowserListDraw(FileBrowser *fb);
static void FileBrowserListQueueDraw(FileBrowser *fb);


/* List Columns */
static FileBrowserColumn *FileBrowserListGetColumn(FileBrowser *fb, gint i);
static FileBrowserColumn *FileBrowserListColumnAppend(
      FileBrowser *fb, const gchar *label, const gint width
);
static void FileBrowserListColumnsClear(FileBrowser *fb);

static void FileBrowserEntrySetSelectedObjects(FileBrowser *fb);


/* Types List */
static void FileBrowserTypesListSetTypes(
      FileBrowser *fb,
      fb_type_struct **list, const gint total
);


/* Callbacks */
static void FileBrowserLocationsPUListItemDestroyCB(gpointer data);
static void FileBrowserTypesPUListItemDestroyCB(gpointer data);
static void FileBrowserIconDestroyCB(gpointer data);
static void FileBrowserObjectDestroyCB(gpointer data);
static void FileBrowserColumnDestroyCB(gpointer data);

static gboolean FileBrowserDragMotionCB(
      GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
      gpointer data
);
static void FileBrowserDragDataReceivedCB(
      GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
      GtkSelectionData *selection_data, guint info, guint t,
      gpointer data
);
static void FileBrowserDragDataGetCB(
      GtkWidget *widget, GdkDragContext *dc,
      GtkSelectionData *selection_data, guint info, guint t,
      gpointer data
);
static void FileBrowserDragDataDeleteCB(
      GtkWidget *widget, GdkDragContext *dc, gpointer data
);

static void FileBrowserShowHiddenObjectsToggleCB(
      GtkWidget *widget, gpointer data
);
static void FileBrowserListFormatToggleCB(
      GtkWidget *widget, gpointer data
);
static void FileBrowserGoToParentCB(GtkWidget *widget, gpointer data);
static void FileBrowserNewDirectoryCB(GtkWidget *widget, gpointer data);
static void FileBrowserRefreshCB(GtkWidget *widget, gpointer data);
static void FileBrowserOKCB(GtkWidget *widget, gpointer data);
static void FileBrowserCancelCB(GtkWidget *widget, gpointer data);

static void FileBrowserLocPUListChangedCB(
      pulistbox_struct *pulistbox, gint i, gpointer data
);

static gint FileBrowserListHeaderEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FileBrowserListEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
);
static void FileBrowserListScrollCB(
      GtkAdjustment *adj, gpointer data
);

static void FileBrowserSelectAllCB(GtkWidget *widget, gpointer data);
static void FileBrowserUnselectAllCB(GtkWidget *widget, gpointer data);
static void FileBrowserInvertSelectionCB(GtkWidget *widget, gpointer data);

static void FileBrowserRenameFPromptCB(
      gpointer data, const gchar *value
);
static void FileBrowserRenameCB(GtkWidget *widget, gpointer data);

static void FileBrowserCHModFPromptCB(
      gpointer data, const gchar *value
);
static void FileBrowserCHModCB(GtkWidget *widget, gpointer data);

static void FileBrowserDeleteCB(GtkWidget *widget, gpointer data);

static gint FileBrowserEntryCompletePathKeyCB(
      GtkWidget *widget, GdkEventKey *key, gpointer data
);
static void FileBrowserEntryEnterCB(GtkWidget *widget, gpointer data);

static void FileBrowserTypesPUListChangedCB(
      pulistbox_struct *pulistbox, gint i, gpointer data
);


/* File Browser Front Ends */
gint FileBrowserInit(void);
void FileBrowserSetStyle(GtkRcStyle *rc_style);
void FileBrowserSetTransientFor(GtkWidget *w);
gboolean FileBrowserIsQuery(void);
void FileBrowserBreakQuery(void);
GtkWidget *FileBrowserGetToplevel(void);
void FileBrowserReset(void);
gboolean FileBrowserGetResponse(
      const gchar *title,
      const gchar *ok_label, const gchar *cancel_label,
      const gchar *path,
      fb_type_struct **type, gint total_types,
      gchar ***path_rtn, gint *path_total_rtns,
      fb_type_struct **type_rtn
);
void FileBrowserMap(void);
void FileBrowserUnmap(void);
void FileBrowserShutdown(void);

const gchar *FileBrowserGetLastLocation(void);
void FileBrowserShowHiddenObjects(const gboolean show);
void FileBrowserListStandard(void);
void FileBrowserListDetailed(void);


/* File Types List */
gint FileBrowserTypeListNew(
      fb_type_struct ***list, gint *total,
      const gchar *ext, /* Space separated list of extensions */
      const gchar *name /* Descriptive name */
);
static void FileBrowserTypeDelete(fb_type_struct *t);
void FileBrowserDeleteTypeList(
      fb_type_struct **list, const gint total
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s) (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)  (((a) > (b)) ? (a) : (b))
#define MIN(a,b)  (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s) (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)

#ifndef ISBLANK
# define ISBLANK(c)     (((c) == ' ') || ((c) == '\t'))
#endif

#define OBJISSEL(fb,n)  ((((fb) != NULL) && ((n) >= 0)) ?   \
 ((g_list_find(fb->selection, (gpointer)(n)) != NULL) ? TRUE : FALSE) : FALSE)


#define FB_WIDTH        480
#define FB_HEIGHT       -1

#define FB_TB_BTN_WIDTH       (20 + 5)
#define FB_TB_BTN_HEIGHT      (20 + 5)

#define FB_LOC_LIST_MAX_CHARS 25    /* Max chars for each item in the
                               * the locations list */

#define FB_LIST_ITEM_MAX_CHARS      25    /* Max chars for each item in the
                               * list */

#define FB_LIST_HEADER_WIDTH  -1
#define FB_LIST_HEADER_HEIGHT (20 + 2)

#define FB_LIST_WIDTH         -1
#define FB_LIST_HEIGHT        150

#define FB_LIST_ICON_WIDTH    20
#define FB_LIST_ICON_HEIGHT   20
#define FB_LIST_ICON_BORDER   2     /* Border between icon and text */

#define FB_DEFAULT_TYPE_STR   "All Files (*.*)"

#define FB_NEW_DIRECTORY_NAME "new_directory"


/*
 *    Icons Data List:
 *
 *    The order of this list must correspond with FileBrowserIconNum.
 *
 *    Each set contains 6 gpointers, and the gpointers in the last
 *    set are all NULL.
 */
#define FB_ICON_DATA_LIST     {     \
      "file",                       \
      icon_file_20x20_xpm,          \
      NULL, NULL, NULL, NULL,       \
                              \
      "file/hidden",                \
      icon_file_hidden_20x20_xpm,   \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/closed",           \
      icon_folder_closed_20x20_xpm, \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/opened",           \
      icon_folder_opened_20x20_xpm, \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/parent",           \
      icon_folder_parent_20x20_xpm, \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/noaccess",         \
      icon_folder_noaccess_20x20_xpm,     \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/home",       \
      icon_folder_home_20x20_xpm,   \
      NULL, NULL, NULL, NULL,       \
                              \
      "directory/hidden",           \
      icon_folder_hidden_20x20_xpm, \
      NULL, NULL, NULL, NULL,       \
                              \
      "link",                       \
      icon_link2_20x20_xpm,         \
      NULL, NULL, NULL, NULL,       \
                              \
      "pipe",                       \
      icon_pipe_20x20_xpm,          \
      NULL, NULL, NULL, NULL,       \
                              \
      "socket",               \
      icon_socket_20x20_xpm,        \
      NULL, NULL, NULL, NULL,       \
                              \
      "device/block",               \
      icon_device_block_20x20_xpm,  \
      NULL, NULL, NULL, NULL,       \
                              \
      "device/character",           \
      icon_device_character_20x20_xpm,\
      NULL, NULL, NULL, NULL,       \
                              \
      "executable",                 \
      icon_executable_20x20_xpm,    \
      NULL, NULL, NULL, NULL,       \
                              \
      "drive/fixed",                \
      icon_drive_fixed_20x20_xpm,   \
      NULL, NULL, NULL, NULL,       \
                              \
      "drive/root",                 \
      icon_drive_root_20x20_xpm,    \
      NULL, NULL, NULL, NULL,       \
                              \
      NULL, NULL,             \
      NULL, NULL, NULL, NULL        \
}


/*
 *    Icon:
 */
struct _FileBrowserIcon {

      GdkPixmap   *pixmap;
      GdkBitmap   *mask;
      gint        width, height;
      gchar       *desc;            /* Used for MIME type */

};


/*
 *    Object:
 */
struct _FileBrowserObject {

      gchar       *name,
                  *full_path,
                  *displayed_name;  /* Name shown on the list
                                     * (may be abbriviated) */
      gint        icon_num;
      gint        x, y,             /* Position and size on list */
                  width, height;

      struct stat lstat_buf;

};


/*
 *    Column:
 *
 *    Used in the FileBrowser's list when displaying things that
 *    need columns.
 */
struct _FileBrowserColumn {

      GtkWidgetFlags    flags;
      gint        position;
      gchar       *label;
      GtkJustification  label_justify;

      /* If the member drag is TRUE then it means this column is
       * being dragged (resized) and the member drag_position
       * indicates the right or lower edge of this column
       */
      gboolean    drag;
      gint        drag_position;
      gint        drag_last_drawn_position;

};


/*
 *    File Browser:
 */
struct _FileBrowser {

      GtkWidget   *toplevel;
      gint        busy_count,
                  freeze_count;
      GtkAccelGroup     *accelgrp;

      GdkCursor   *cur_busy,
                  *cur_column_hresize,
                  *cur_translate;

      GtkWidget   *main_vbox,
                  *goto_parent_btn,
                  *new_directory_btn,
                  *rename_btn,
                  *refresh_btn,
                  *show_hidden_objects_tb,
                  *list_format_standard_tb,
                  *list_format_vertical_tb,
                  *list_format_vertical_details_tb,
                  *list_header_da,  /* List's header GtkDrawingArea */
                  *list_da,   /* List's GtkDrawingArea */
                  *list_vsb,  /* List's vertical GtkScrollBar */
                  *list_hsb,  /* List's horizontal GtkScrollBar */
                  *list_menu, /* List's right-click GtkMenu */
                  *entry,           /* Location GtkEntry */
                  *ok_btn_label,
                  *ok_btn,
                  *cancel_btn_label,
                  *cancel_btn;

      gboolean    vsb_map_state,
                  hsb_map_state;

      GdkPixmap   *list_pm;

      pulistbox_struct  *loc_pulistbox,
                        *types_pulistbox;

      gchar       *cur_location;

      gint        block_loop_level;
      gboolean    user_response;    /* TRUE if user clicked on OK */

      FileBrowserListFormat   list_format;

      FileBrowserColumn **column;
      gint              total_columns;

      fb_type_struct    cur_type;   /* Current file type */
      gchar             **selected_path;
      gint        total_selected_paths;

      FileBrowserIcon   **icon;
      gint        total_icons;

      gint        uid, euid;
      gint        gid, egid;
      gchar       *home_path;
      gchar       **drive_path;
      gint        total_drive_paths;

      gint        focus_object;
      GList       *selection,
                  *selection_end;
      FileBrowserObject **object;
      gint        total_objects;
      gint        objects_per_row;  /* For use with
                                     * FB_LIST_FORMAT_STANDARD */
      gint        last_single_select_object;
      gboolean    show_hidden_objects;

      /* Last button pressed and pointer position */
      gint        button,
                  drag_last_x,
                  drag_last_y;
      gulong            last_button1_release_time;    /* In ms */

};

static FileBrowser file_browser;


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
      if(s != NULL) {
          if(s2 != NULL) {
            gchar *sr = g_strconcat(s, s2, NULL);
            g_free(s);
            s = sr;
          }
      } else {
          if(s2 != NULL)
            s = STRDUP(s2);
          else
            s = STRDUP("");
      }
      return(s);
}


/*
 *    Returns a list of full paths parsed from the given DND buffer.
 */
static gchar **FileBrowserDNDBufParse(
      const gchar *buf, const gint len, gint *n
)
{
      const gchar *buf_end = buf + len;   /* Record end of buffer */
      gchar **strv = NULL;
      gint i, strc = 0;

      if(n != NULL)
          *n = strc;

      if((buf == NULL) || (len <= 0))
          return(strv);

      /* Iterate through buffer that contains a list of '\0' character
       * separated strings
       */
      while(buf < buf_end)
      {
          const gchar *s = buf;
          gint s_len = STRLEN(s);

          /* Is the protocol prefix of the string one that we want? */
          if(g_strcasepfx(s, "file://"))
          {
            /* Seek past protocol "file://" */
            s += STRLEN("file://");

            /* Seek past "username:password@host:port"
             * too many programs fail to do this!
             */
            s = (s != NULL) ? strchr(s, '/') : NULL;

            /* Able to seek to full path portion of the string? */
            if(s != NULL)
            {
                /* Got full path portion, now add it to the list of
                 * return strings
                 */
                i = strc;
                strc = i + 1;
                strv = (gchar **)g_realloc(
                  strv, strc * sizeof(gchar *)
                );
                if(strv == NULL)
                {
                  strc = 0;
                  break;
                }
                strv[i] = STRDUP(s);
            }
          }

          /* Seek buf to next string.  First seek buf past this string
           * then seek past any '\0' deliminator(s) until we reach
           * the next string or the end of the buffer
           */
          buf += s_len;
          while((buf < buf_end) ? (*buf == '\0') : FALSE)
            buf++;
      }

      /* Update total return value */
      if(n != NULL)
          *n = strc;

      return(strv);
}

/*
 *    Generates a DND buffer based on the list of objects that are
 *    selected based on the given selection.
 */
static gchar *FileBrowserDNDBufFormat(
      FileBrowserObject **object, const gint total, GList *selection,
      gint *len
)
{
      gchar *buf = NULL;
      gint i, buf_len = 0;
      GList *glist;
      FileBrowserObject *o;


      /* Iterate through selection */
      for(glist = selection; glist != NULL; glist = g_list_next(glist))
      {
          i = (gint)glist->data;
          if((i >= 0) && (i < total))
            o = object[i];
          else
            o = NULL;
          if((o != NULL) ? (o->full_path != NULL) : FALSE)
          {
            gchar *url = g_strdup_printf(
                "file://%s",
                o->full_path
            );
            gint url_len = STRLEN(url);
            gint last_buf_len = buf_len;

            /* Increase allocation of buffer for this url string
             * which we will append to the buffer (include the
             * '\0' character counted in buf_len)
             */
            buf_len += url_len + 1;
            buf = (gchar *)g_realloc(buf, buf_len * sizeof(gchar));
            if(buf == NULL)
            {
                buf_len = 0;
                break;
            }
            memcpy(
                buf + last_buf_len,
                (url != NULL) ? url : "",
                url_len + 1         /* Include the '\0' character */
            );

            g_free(url);
          }
      }

      /* Update return value */
      if(len != NULL)
          *len = buf_len;

      return(buf);
}


/*
 *    Checks if the object name specified by name and full_path
 *    match the extensions given in ext.
 */
static gboolean FileBrowserObjectNameFilter(
      const gchar *name, const gchar *full_path,
      const gchar *ext
)
{
      gboolean use_regex;
      gchar *st, *st_end, cur_ext[80];
      gint cur_ext_len, name_len;

      if((name == NULL) || (full_path == NULL) || (ext == NULL))
          return(FALSE);

      name_len = STRLEN(name);

      while(ISBLANK(*ext) || (*ext == ','))
          ext++;

      /* Iterate through each extension in ext */
      while(*ext != '\0')
      {
          use_regex = FALSE;

          /* Copy current extension string in ext to cur_ext and
           * seek ext to next position
           */
          cur_ext_len = 0;
          st = cur_ext;
          st_end = cur_ext + 79;    /* Set end 1 character premature */
          while((st < st_end) && !ISBLANK(*ext) && (*ext != ',') &&
              (*ext != '\0')
          )
          {
            /* Use this opportunity to check if there are characters
             * in the extension string to warrent the use of regex
             */
            if((*ext == '*') || (*ext == '?'))
                use_regex = TRUE;

            *st++ = *ext++;
            cur_ext_len++;
          }
          *st = '\0';

          /* Seek ext to next extension string */
          while(ISBLANK(*ext) || (*ext == ','))
            ext++;

          /* Check if there is a match */
#if defined(_WIN32)
          if(name_len >= cur_ext_len)
          {
            if(!g_strcasecmp(
                name + name_len - cur_ext_len,
                cur_ext
            ))
            return(TRUE);
          }
#else
          if(use_regex)
          {
            /* Use regex to match */
            if(!fnmatch(cur_ext, name, 0))
                return(TRUE);
          }
          else
          {
            /* Check if cur_ext is a postfix of name */
            if(name_len >= cur_ext_len)
            {
                if(!g_strcasecmp(
                  name + name_len - cur_ext_len,
                  cur_ext
                ))
                  return(TRUE);
            }
          }
#endif
      }
      return(FALSE);
}


/*
 *    Returns a list of strings describing the drive paths.
 */
static gchar **FileBrowserGetDrivePaths(gint *n)
{
#if defined(_WIN32)
      /* Win32 */
      gchar drive_letter = 'a';
      gchar drive_name[10];
      gint i, strc = 0;
      gchar **strv = NULL;

      for(drive_letter = 'a'; drive_letter <= 'g'; drive_letter++)
      {
          g_snprintf(
            drive_name, sizeof(drive_name),
            "%c:\\",
            toupper(drive_letter)
          );
          i = strc;
          strc = i + 1;
          strv = (gchar **)g_realloc(
            strv, strc * sizeof(gchar *)
          );
          if(strv == NULL)
          {
            strc = 0;
            break;
          }

          strv[i] = STRDUP(drive_name);
      }

      if(n != NULL)
          *n = strc;

      return(strv);
#elif defined(__FreeBSD__)
      /* FreeBSD */
      if(n != NULL )
          *n = 0;
      return(NULL);
#else
      /* UNIX */
      gint i, strc = 0;
      gchar **strv = NULL;
#ifdef __SOLARIS__
      struct vfstab *vfs_ptr = NULL;
      int mtback;
#else
      struct mntent *mt_ptr;
#endif

      /* Open system devices list file */
#ifdef __SOLARIS__
      FILE *fp = fopen("/etc/vfstab", "rb");
#else
      FILE *fp = setmntent("/etc/fstab", "rb");
#endif
      if(fp == NULL)
      {
          if(n != NULL)
            *n = strc;
          return(strv);
      }

      /* Begin reading system devices list file */
#ifdef __SOLARIS__
      vfs_ptr = (struct vfstab *)g_malloc(sizeof(struct vfstab));
      mtback = getvfsent(fp, vfs_ptr);
      while(mtback != 0)
#else
      mt_ptr = getmntent(fp);
      while(mt_ptr != NULL)
#endif
      {
          i = strc;
          strc = i + 1;
          strv = (gchar **)g_realloc(
            strv, strc * sizeof(gchar *)
          );
          if(strv == NULL)
          {
            strc = 0;
            break;
          }

          /* Get mount path as the drive path */
#ifdef __SOLARIS__
          strv[i] = STRDUP(vfs_ptr->vfs_mountp);
#else
          strv[i] = STRDUP(mt_ptr->mnt_dir);
#endif

          /* Read next mount entry */
#ifdef __SOLARIS__
          mtback = getmntent(fp, vfs_ptr);
#else
          mt_ptr = getmntent(fp);
#endif
      }


      /* Close the system devices list file */
#ifdef __SOLARIS__
      fclose(fp);
      fp = NULL;
      vfs_ptr = NULL;
#else
      endmntent(fp);
      fp = NULL;
#endif

      if(n != NULL)
          *n = strc;

      return(strv);
#endif
}


/*
 *    Sets the file browser busy or ready.
 */
static void FileBrowserSetBusy(FileBrowser *fb, const gboolean busy)
{
      GdkCursor *cursor;
      GtkWidget *w;

      if(fb == NULL)
          return;

      w = fb->toplevel;
      if(w != NULL)
      {
          if(busy)
          {
            /* Increase busy count */
            fb->busy_count++;

            /* If already busy then don't change anything */
            if(fb->busy_count > 1)
                return;

            cursor = fb->cur_busy;
          }
          else
          {
            /* Reduce busy count */
            fb->busy_count--;
            if(fb->busy_count < 0)
                fb->busy_count = 0;

            /* If still busy do not change anything */
            if(fb->busy_count > 0)
                return;

            cursor = NULL;    /* Use default cursor */
          }

          /* Update toplevel window's cursor */
          if(w->window != NULL)
          {
            gdk_window_set_cursor(w->window, cursor);
            gdk_flush();
          }
      }
}


/*
 *    Sets the showing of hidden objects and updates the list as
 *    needed.
 */
static void FileBrowserSetShowHiddenObjects(
      FileBrowser *fb, const gboolean show
)
{
      if(fb == NULL)
          return;

      /* Skip if there is no change */
      if(fb->show_hidden_objects == show)
          return;

      fb->freeze_count++;

      fb->show_hidden_objects = show;
      GTK_TOGGLE_BUTTON_SET_ACTIVE(
          fb->show_hidden_objects_tb, show
      );

      /* Update the list */
      FileBrowserListUpdate(fb, NULL);

      fb->freeze_count--;
}


/*
 *    Sets the list format and updates the list as needed.
 */
static void FileBrowserSetListFormat(
      FileBrowser *fb, const FileBrowserListFormat list_format
)
{
      GtkWidget *w, *toplevel;
      FileBrowserColumn *column;

      if(fb == NULL)
          return;

      /* Skip if there is no change */
      if(fb->list_format == list_format)
          return;

      fb->freeze_count++;

      toplevel = fb->toplevel;

      /* Set new list format */
      fb->list_format = list_format;

      /* Update list format toggle buttons, make sure we freeze so the
       * toggle callback does not recurse
       */
      switch(list_format)
      {
        case FB_LIST_FORMAT_VERTICAL:
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_standard_tb, FALSE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_tb, TRUE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_details_tb, FALSE
          );
          break;
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_standard_tb, FALSE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_tb, FALSE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_details_tb, TRUE
          );
          break;
        case FB_LIST_FORMAT_STANDARD:
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_standard_tb, TRUE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_tb, FALSE
          );
          GTK_TOGGLE_BUTTON_SET_ACTIVE(
            fb->list_format_vertical_details_tb, FALSE
          );
          break;
      }

      /* Begin updating things to recognize the new list format */

      /* Update list columns */
      FileBrowserListColumnsClear(fb);
      switch(list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
          /* Set up list columns */
          /* Name */
          column = FileBrowserListColumnAppend(
            fb, "Name", 145
          );
          /* Size */
          column = FileBrowserListColumnAppend(
            fb, "Size", 70
          );
          column->label_justify = GTK_JUSTIFY_RIGHT;
          /* Permissions */
          column = FileBrowserListColumnAppend(
            fb, "Permissions", 78
          );
          /* Last Modified */
          column = FileBrowserListColumnAppend(
            fb, "Last Modified", 160
          );

          /* Show list header */
          w = fb->list_header_da;
          if(w != NULL)
            gtk_widget_show(w);
          break;

        case FB_LIST_FORMAT_VERTICAL:
        case FB_LIST_FORMAT_STANDARD:
          /* Hide list header */
          w = fb->list_header_da;
          if(w != NULL)
            gtk_widget_hide(w);
          break;
      }
      gtk_widget_queue_resize(toplevel);


      /* Update the list */
      FileBrowserListUpdate(fb, NULL);

      fb->freeze_count--;
}

/*
 *    Sets the new location and updates the locations popup list.
 *
 *    The path specifies the full path to the new location. If
 *    path does not lead to a directory then its parent will
 *    be used instead. If the specified location and the current
 *    location are the same then the objects lists will not be
 *    updated.
 */
static void FileBrowserSetLocation(FileBrowser *fb, const gchar *path)
{
      gchar *new_location, *new_name;
      GtkWidget *w;

      if((fb == NULL) || (path == NULL))
          return;

      if(path == fb->cur_location)
          return;

      /* Make a copy of the given path which will be modified and
       * checked before (if) it is finally accepted
       */
      new_location = STRDUP(path);
      new_name = NULL;


      /* Special notation checks */

      /* Current location? */
      if(!strcmp(new_location, "."))
      {
          g_free(new_location);
#ifdef _WIN32
          new_location = (fb->cur_location != NULL) ?
            STRDUP(fb->cur_location) : STRDUP("C:");
#else
          new_location = (fb->cur_location != NULL) ?
            STRDUP(fb->cur_location) : STRDUP("/");
#endif
      }

      /* Parent? */
      if(!strcmp(new_location, ".."))
      {
          g_free(new_location);
#ifdef _WIN32
          new_location = (fb->cur_location != NULL) ?
            g_dirname(fb->cur_location) : STRDUP("C:");
#else
          new_location = (fb->cur_location != NULL) ?
            g_dirname(fb->cur_location) : STRDUP("/");
#endif
      }

      /* Home directory prefix? */
      if(*new_location == '~')
      {
          /* Prefix the home directory to the new location path */
          const gchar *home = fb->home_path;
          gchar *s = g_strdup_printf(
            "%s%s",
            (home != NULL) ? home : "",
            new_location + 1
          );
          g_free(new_location);
          new_location = s;
      }

      /* At this point we need to check if the new location is an
       * absolute path, if it is not then the current working dir
       * must be prefixed to it
       */
      if(!g_path_is_absolute(new_location))
      {
          gchar *s, *cwd = STRDUP(g_get_current_dir());
          s = STRDUP(PrefixPaths(cwd, new_location));
          g_free(new_location);
          new_location = s;
          g_free(cwd);
      }

      /* Make sure that the new location is a directory, if it is
       * not then use its parent
       */
      if(!ISPATHDIR(new_location))
      {
          gchar *parent = g_dirname(new_location);
          const gchar *name = strrchr(new_location, G_DIR_SEPARATOR);

          /* Record that we have an object name */
          g_free(new_name);
          new_name = (name != NULL) ? STRDUP(name + 1) : NULL;

          /* Record parent directory */
          g_free(new_location);
          new_location = parent;
      }

      /* Strip tailing deliminator */
      if(new_location != NULL)
      {
#ifdef _WIN32
          gchar *s = strrchr(new_location, G_DIR_SEPARATOR);
          gint prefix_len = STRLEN("x:\\");
#else
          gchar *s = strrchr(new_location, G_DIR_SEPARATOR);
          gint prefix_len = STRLEN("/");
#endif
          while(s >= (new_location + prefix_len))
          {
            if((s[0] == G_DIR_SEPARATOR) &&
               (s[1] == '\0')
            )
                *s = '\0';
            else
                break;
            s--;
          }
      }

      /* No change in the location? */
      if((fb->cur_location != NULL) ?
          !strcmp(fb->cur_location, new_location) : FALSE
      )
      {
          /* No change in location, but check if we still need to
           * update the lists if the lists were reset/cleared
           */
          if(fb->total_objects > 0)
          {
            /* Lists were not cleared, so no need to update */
            g_free(new_location);
            g_free(new_name);
            return;
          }
      }

      /* Accept and set the new location */
      g_free(fb->cur_location);
      fb->cur_location = new_location;
      new_location = NULL;

      /* Update file name entry */
      w = fb->entry;
      if(w != NULL)
      {
          gtk_entry_set_text(
            GTK_ENTRY(w),
            (!STRISEMPTY(new_name)) ? new_name : ""
          );
          gtk_entry_set_position(GTK_ENTRY(w), -1);
      }

      /* Get the listing of the objects at the new location */
      FileBrowserListUpdate(fb, NULL);

      /* Update the locations popup list */
      FileBrowserLocationsPUListUpdate(fb);

      g_free(new_location);
      g_free(new_name);
}

/*
 *    Returns the current location, never returns NULL.
 */
static const gchar *FileBrowserGetLocation(FileBrowser *fb)
{
#ifdef _WIN32
      return((fb != NULL) ? fb->cur_location : "C:\\");
#else
      return((fb != NULL) ? fb->cur_location : "/");
#endif
}


/*
 *    DND "drag_motion" signal callback.
 */
static gboolean FileBrowserDragMotionCB(
      GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
      gpointer data
)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if((dc == NULL) || (fb == NULL))
          return(FALSE);

      if(dc->actions & GDK_ACTION_COPY)
          gdk_drag_status(dc, GDK_ACTION_COPY, t);
      else
          gdk_drag_status(dc, 0, t);

      return(TRUE);
}

/*
 *    DND "drag_data_received" signal callback.
 */
static void FileBrowserDragDataReceivedCB(
      GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
      GtkSelectionData *selection_data, guint info, guint t,
      gpointer data
)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if((widget == NULL) || (dc == NULL) || (fb == NULL))
          return;

      if(selection_data == NULL)
          return;

      /* Make sure the DND info is one that we want */
      if((info == 0) || (info == 1) || (info == 2))
      {
          /* Handle by destination widget */
          /* List */
          if(widget == fb->list_da)
          {
            /* When dropping to the list, first check if there is
             * exactly one object and if so then check if it leads
             * to a directory, in which case we will set the new
             * location.  Otherwise we just set it to the file
             * name entry.
             */
            gint i, strc;
            gchar **strv = FileBrowserDNDBufParse(
                (const gchar *)selection_data->data,
                selection_data->length,
                &strc
            );
            const gchar *new_location = (strc == 1) ? strv[0] : NULL;
            if((new_location != NULL) ? ISPATHDIR(new_location) : FALSE)
            {
                /* Exactly one object dropped and it leads to a
                 * directory so go to the new location
                 */
                FileBrowserSetLocation(fb, new_location);
            }
            else
            {
                /* Not one object or the one object does not lead to
                 * a directory, so just set the dropped objects to
                 * the file name entry.
                 */
                GtkEntry *entry = GTK_ENTRY(fb->entry);
                gchar *s = NULL;
                for(i = 0; i < strc; i++)
                {
                  s = G_STRCAT(s, strv[i]);
                  if((i + 1) < strc)
                      s = G_STRCAT(s, ",");
                }
                gtk_entry_set_text(entry, s);
                gtk_entry_set_position(entry, -1);
                g_free(s);
            }
            for(i = 0; i < strc; i++)
                g_free(strv[i]);
            g_free(strv);
          }
          /* File name entry */
          else if(widget == fb->entry)
          {
            GtkEntry *entry = GTK_ENTRY(widget);
            gchar *s = NULL;
            gint i, strc;
            gchar **strv = FileBrowserDNDBufParse(
                (const gchar *)selection_data->data,
                selection_data->length,
                &strc
            );
            for(i = 0; i < strc; i++)
            {
                s = G_STRCAT(s, strv[i]);
                if((i + 1) < strc)
                  s = G_STRCAT(s, ",");
                g_free(strv[i]);
            }
            g_free(strv);
            gtk_entry_set_text(entry, s);
            gtk_entry_set_position(entry, -1);
            g_free(s);
          }
      }
}

/*
 *    DND "drag_data_get" signal callback.
 */
static void FileBrowserDragDataGetCB(
      GtkWidget *widget, GdkDragContext *dc,
      GtkSelectionData *selection_data, guint info, guint t,
      gpointer data
)
{
      gboolean data_sent = FALSE;
      FileBrowser *fb = FILE_BROWSER(data);
      if((widget == NULL) || (fb == NULL))
          return;

      /* Make sure the DND info is one that we support */
      if((info == 0) || (info == 1) || (info == 2))
      {
          /* Handle by destination widget */
          /* List */
          if(widget == fb->list_da)
          {
            gint buf_len;
            gchar *buf = FileBrowserDNDBufFormat(
                fb->object, fb->total_objects, fb->selection,
                &buf_len
            );
            if(buf != NULL)
            {
                /* Send out DND data */
                gtk_selection_data_set(
                  selection_data,
                  GDK_SELECTION_TYPE_STRING,
                  8,    /* 8 bits per character */
                  buf, buf_len
                );
                data_sent = TRUE;
                g_free(buf);
            }
          }
      }

      /* Failed to send out DND data? */
      if(!data_sent)
      {
          /* Send a response indicating error */
          const gchar *s = "Error";
          gtk_selection_data_set(
            selection_data,
            GDK_SELECTION_TYPE_STRING,
            8,          /* 8 bits per character */
            s,
            STRLEN(s) + 1     /* Include the '\0' character */
          );
          data_sent = TRUE;
      }
}

/*
 *    DND "drag_data_delete" signal callback.
 */
static void FileBrowserDragDataDeleteCB(
      GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if((widget == NULL) || (fb == NULL))
          return;

      /* Typical this means that some objects have been deleted
       * so we need to refresh.
       */
      FileBrowserRefreshCB(fb->refresh_btn, fb);
}

/*
 *    Show hidden objects GtkToggleButton "toggled" signal callback.
 */
static void FileBrowserShowHiddenObjectsToggleCB(
      GtkWidget *widget, gpointer data
)
{
      GtkToggleButton *tb = GTK_TOGGLE_BUTTON(widget);
      FileBrowser *fb = FILE_BROWSER(data);
      if((tb == NULL) || (fb == NULL))
          return;

      if(fb->freeze_count > 0)
          return;

      fb->freeze_count++;

      if(fb->show_hidden_objects == gtk_toggle_button_get_active(tb))
      {
          fb->freeze_count--;
          return;
      }

      fb->show_hidden_objects = tb->active;

      /* Update the listing */
      FileBrowserListUpdate(fb, NULL);

      fb->freeze_count--;
}

/*
 *    List format GtkToggleButton "toggled" signal callback.
 */
static void FileBrowserListFormatToggleCB(
      GtkWidget *widget, gpointer data
)
{
      FileBrowserListFormat list_format = FB_LIST_FORMAT_STANDARD;
      GtkToggleButton *tb = GTK_TOGGLE_BUTTON(widget);
      FileBrowser *fb = FILE_BROWSER(data);
      if((tb == NULL) || (fb == NULL))
          return;

      if(fb->freeze_count > 0)
          return;

      fb->freeze_count++;

      /* Do not let toggle button become untoggled when toggling
       * was not frozen
       */
      if(!tb->active)
      {
          gtk_toggle_button_set_active(tb, TRUE);
          fb->freeze_count--;
          return;
      }

      /* See which toggle button this signal was for so we know
       * what list format to set
       */
      /* Standard */
      if(widget == fb->list_format_standard_tb)
          list_format = FB_LIST_FORMAT_STANDARD;
      /* Vertical */
      else if(widget == fb->list_format_vertical_tb)
          list_format = FB_LIST_FORMAT_VERTICAL;
      /* Vertical with details */
      else if(widget == fb->list_format_vertical_details_tb)
          list_format = FB_LIST_FORMAT_VERTICAL_DETAILS;

      /* Set new list format and reget listing */
      FileBrowserSetListFormat(fb, list_format);

      fb->freeze_count--;
}

/*
 *    Go to parent callback.
 */
static void FileBrowserGoToParentCB(GtkWidget *widget, gpointer data)
{
      const gchar *cur_location;
      gchar *parent;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      /* Get current location, then get its parent */
      cur_location = FileBrowserGetLocation(fb);
      parent = (cur_location != NULL) ?
          g_dirname(cur_location) : NULL;

      /* Go to the parent of the current location */
      if(parent != NULL)
          FileBrowserSetLocation(fb, parent);

      g_free(parent);
}

/*
 *    New Directory callback.
 */
static void FileBrowserNewDirectoryCB(GtkWidget *widget, gpointer data)
{
      mode_t m;
      gchar *name, *full_path;
      const gchar *cur_location;
      GtkWidget *toplevel;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_MKDIR_FAILED   "Cree Guía Nueva Fallada"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_MKDIR_FAILED   "Créer Le Nouvel Annuaire Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_MKDIR_FAILED   "Schaffen Sie Neues Versagten Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_MKDIR_FAILED   "Creare L'Elenco Nuovo Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_MKDIR_FAILED   "Creëer Nieuwe Gids Verzuimde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_MKDIR_FAILED   "Crie Novo Guia Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_MKDIR_FAILED   "Skap New Directory Failed"
#else
# define TITLE_MKDIR_FAILED   "Create New Directory Failed"
#endif

#define MESSAGE_MKDIR_FAILED(s)           {     \
 CDialogSetTransientFor(toplevel);        \
 CDialogGetResponse(                      \
  TITLE_MKDIR_FAILED,                     \
  (s), NULL,                              \
  CDIALOG_ICON_WARNING,                   \
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK  \
 );                                 \
 CDialogSetTransientFor(NULL);                  \
}

      toplevel = fb->toplevel;
      cur_location = FileBrowserGetLocation(fb);
      if(cur_location == NULL)
          return;

      FileBrowserSetBusy(fb, TRUE);

      /* Generate new name and full path */
      name = STRDUP(FB_NEW_DIRECTORY_NAME);
      full_path = STRDUP(PrefixPaths(cur_location, name));

#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
      m =   S_IRUSR | S_IWUSR | S_IXUSR |
            S_IRGRP | S_IWGRP | S_IXGRP |
            S_IROTH | S_IWOTH | S_IXOTH;
#else
      m = 0;
#endif
      /* Create new directory */
#ifdef _WIN32
      if(mkdir(full_path))
#else
      if(mkdir(full_path, m))
#endif
      {
          gint i, error_code;
          gboolean created = FALSE;

          /* Failed to create, try creating it under a different
           * name.
           */
          for(i = 2; i < 100; i++)
          {
            /* Regenerate new name and full path */
            g_free(name);
            g_free(full_path);
            name = g_strdup_printf(
                "%s%i",
                FB_NEW_DIRECTORY_NAME, i
            );
            full_path = STRDUP(PrefixPaths(cur_location, name));

            /* Try to create new directory, if success then
             * break out of the loop.
             */
#ifdef _WIN32
            if(!mkdir(full_path))
#else
            if(!mkdir(full_path, m))
#endif
            {
                created = TRUE;
                break;
            }

            /* Error creating directory and it was not because it
             * already exists?
             */
            error_code = (gint)errno;
            if(error_code != EEXIST)
            {
                gchar *s = g_strdup_printf(
                  "%s.",
                  g_strerror(error_code)
                );
                MESSAGE_MKDIR_FAILED(s);
                g_free(s);
                break;
            }
          }
          /* Failed to create new directory? */
          if(!created)
          {
            g_free(name);
            g_free(full_path);
            FileBrowserSetBusy(fb, FALSE);
            return;
          }
      }

      /* Update the list */
      FileBrowserListUpdate(fb, NULL);

      /* Select the newly created directory */
      if(name != NULL)
      {
          gint i;
          const FileBrowserObject *o;
          for(i = 0; i < fb->total_objects; i++)
          {
            o = fb->object[i];
            if((o != NULL) ? (o->name == NULL) : TRUE)
                continue;

            if(!strcmp(o->name, name))
            {
                /* Select this object */
                fb->focus_object = i;
                fb->selection = g_list_append(
                  fb->selection, (gpointer)i
                );
                fb->selection_end = g_list_last(fb->selection);

                /* Scroll to this object */
                FileBrowserListMoveToObject(fb, i, 0.5f);

                break;
            }
          }
      }
      FileBrowserListQueueDraw(fb);

      /* Update selected objects on entry */
      FileBrowserEntrySetSelectedObjects(fb);

      FileBrowserSetBusy(fb, FALSE);

      g_free(name);
      g_free(full_path);
#undef MESSAGE_MKDIR_FAILED
#undef TITLE_MKDIR_FAILED
}

/*
 *    Refresh callback.
 */
static void FileBrowserRefreshCB(GtkWidget *widget, gpointer data)
{
      gint last_scroll_x = 0, last_scroll_y = 0;
      GtkWidget *w;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      FileBrowserSetBusy(fb, TRUE);

      /* Record the last scroll positions */
      w = fb->list_hsb;
      if(w != NULL)
          last_scroll_x = (gint)GTK_ADJUSTMENT_GET_VALUE(
            GTK_RANGE(w)->adjustment
          );
      w = fb->list_vsb;
      if(w != NULL)
          last_scroll_y = (gint)GTK_ADJUSTMENT_GET_VALUE(
            GTK_RANGE(w)->adjustment  
          );


      /* Begin refreshing */

      /* Reget the locations list */
      FileBrowserLocationsPUListUpdate(fb);

      /* Reget the objects list */
      FileBrowserListUpdate(fb, NULL);

      /* Restore the GtkScrollBar positions */
      w = fb->list_hsb;
      if(w != NULL)
      {
          GtkRange *range = GTK_RANGE(w);
          GtkAdjustment *adj = range->adjustment;
          if(adj != NULL)
            gtk_adjustment_set_value(
                adj,
                CLIP(
                  (gfloat)last_scroll_x,
                  adj->lower,
                  MAX(adj->upper - adj->page_size, adj->lower)
                )
            );
      }
      w = fb->list_vsb;
      if(w != NULL)
      {
          GtkRange *range = GTK_RANGE(w);
          GtkAdjustment *adj = range->adjustment;
          if(adj != NULL)
            gtk_adjustment_set_value(
                adj,
                CLIP(
                  (gfloat)last_scroll_y,
                  adj->lower,
                  MAX(adj->upper - adj->page_size, adj->lower)
                )
            );
      }

      FileBrowserSetBusy(fb, FALSE);
}


/*
 *    Updates the locations popup list based on the current
 *    location.
 */
static void FileBrowserLocationsPUListUpdate(FileBrowser *fb)
{
      struct stat stat_buf;
      const gchar *cur_location;
      pulist_struct *pulist;
      pulistbox_struct *pulistbox;
      const FileBrowserIcon *icon;
      GtkDestroyNotify destroy_cb = FileBrowserLocationsPUListItemDestroyCB;

      if(fb == NULL)
          return;

      pulistbox = fb->loc_pulistbox;
      pulist = PUListBoxGetPUList(pulistbox);
      if(pulist == NULL)
          return;

      FileBrowserSetBusy(fb, TRUE);

      /* Delete all the existing items */
      PUListClear(pulist);

#define GET_ICON(p)     {           \
 if(stat((p), &stat_buf))           \
  icon = NULL;                      \
 else                         \
  icon = FileBrowserGetIcon(        \
   fb,                              \
   FileBrowserMatchIconNumFromPath( \
    fb, (p), &stat_buf              \
   )                          \
  );                          \
}

/* Takes string s and shortens it, allocating a new string s2
 * that is a shortened (as needed) version of s.  The string s2
 * must be deleted
 */
#define ALLOC_STRING_SHORTENED            \
{ if(s != NULL) {             \
 const gint len = STRLEN(s),        \
          max_characters = FB_LOC_LIST_MAX_CHARS;\
                              \
 /* Length of s is too long? */           \
 if(len > max_characters)           \
  s2 = g_strdup_printf(             \
   "...%s",                   \
   &s[len - max_characters + 3]           \
  );                          \
 else                         \
  s2 = STRDUP(s);             \
} else {                      \
 s2 = NULL;                   \
} }

      /* Get the current location and set the items for it as
       * each of its parents
       */
      cur_location = FileBrowserGetLocation(fb);
      if(cur_location != NULL)
      {
          gint i, icon_num;
          gchar   *s = STRDUP(cur_location),
                  *sd = strrchr(s, G_DIR_SEPARATOR),
                  *s2;

          /* Current location */
          if(stat(s, &stat_buf))
            icon_num = -1;
          else
            icon_num = FileBrowserMatchIconNumFromPath(
                fb, s, &stat_buf
            );
          if(icon_num == FB_ICON_FOLDER_CLOSED)
            icon_num = FB_ICON_FOLDER_OPENED;
          icon = FileBrowserGetIcon(fb, icon_num);
          ALLOC_STRING_SHORTENED
          if(icon != NULL)
            i = PUListAddItemPixText(
                pulist, s2, icon->pixmap, icon->mask
            );
          else
            i = PUListAddItem(pulist, s2);
          g_free(s2);
          PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);

          /* Parent locations */
#ifdef _WIN32
          while(sd > (s + 3))
#else
          while(sd > s)
#endif
          {
            *sd = '\0';

            GET_ICON(s);
            ALLOC_STRING_SHORTENED
            if(icon != NULL)
                i = PUListAddItemPixText(
                  pulist, s2, icon->pixmap, icon->mask
                );
            else
                i = PUListAddItem(pulist, s2);
            g_free(s2);
            PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);

            sd = strrchr(s, G_DIR_SEPARATOR);
          }

          /* Toplevel */
#ifdef _WIN32
          /* On Win32, use s as is from the above parent fetching loop */
          sd = strchr(s, G_DIR_SEPARATOR);
          if((sd != NULL) ? (*(sd + 1) != '\0') : FALSE)
          {
            *(sd + 1) = '\0';
            GET_ICON(s);
            ALLOC_STRING_SHORTENED
            if(icon != NULL)
                i = PUListAddItemPixText(
                  pulist, s2, icon->pixmap, icon->mask
                );
            else
                i = PUListAddItem(pulist, s2);
            g_free(s2);
            PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
          }
          g_free(s);
#else
          g_free(s);
          s = STRDUP("/");
          GET_ICON(s);
          ALLOC_STRING_SHORTENED
          if(icon != NULL)
            i = PUListAddItemPixText(
                pulist, s2, icon->pixmap, icon->mask
            );
          else
            i = PUListAddItem(pulist, s2);
          g_free(s2);
          PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
          g_free(s);
#endif
      }

#ifndef _WIN32
      /* Home */
      if(fb->home_path != NULL)
      {
          const gchar *s = fb->home_path;
          gint i;
          gchar *s2;
          GET_ICON(s);
          ALLOC_STRING_SHORTENED
          if(icon != NULL)
            i = PUListAddItemPixText(
                pulist, s2, icon->pixmap, icon->mask
            );
          else
            i = PUListAddItem(pulist, s2);
          g_free(s2);
          PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
      }
#endif

      /* Drives */
      if(fb->total_drive_paths > 0)
      {
          gint i, i2;
          const gchar *s;
          gchar *s2;

          for(i = 0; i < fb->total_drive_paths; i++)
          {
            s = fb->drive_path[i];
            if(s == NULL)
                continue;

            /* Ignore toplevel */
            if(!strcmp(s, "/"))
                continue;

#ifndef _WIN32
            /* Ignore drives that do not have absolute paths */
            if(!g_path_is_absolute(s))
                continue;
#endif

#if defined(_WIN32)
            icon = FileBrowserGetIcon(fb, FB_ICON_DRIVE_FIXED);
#else
            GET_ICON(s);
#endif
            ALLOC_STRING_SHORTENED
            if(icon != NULL)
                i2 = PUListAddItemPixText(
                  pulist, s2, icon->pixmap, icon->mask
                );
            else
                i2 = PUListAddItem(
                  pulist, s2
                );
            g_free(s2);
            PUListSetItemDataFull(pulist, i2, STRDUP(s), destroy_cb);
          }

      }

/* Other things to be added to the popup list, like mounted drives? */


      /* Make sure that no more than 10 lines are visible */
      PUListBoxSetLinesVisible(
          pulistbox,
          MIN(PUListGetTotalItems(pulist), 10)
      );

      /* Select the first item */
      PUListBoxSelect(pulistbox, 0);


      FileBrowserSetBusy(fb, FALSE);

#undef GET_ICON
#undef ALLOC_STRING_SHORTENED
}


/*
 *    Returns the icon at index i or NULL on error.
 */
static FileBrowserIcon *FileBrowserGetIcon(FileBrowser *fb, const gint i)
{
      if(fb == NULL)
          return(NULL);

      if((i < 0) || (i >= fb->total_icons))
          return(NULL);
      else
          return(fb->icon[i]);
}

/*
 *    Appends an icon to the list.
 */
static FileBrowserIcon *FileBrowserIconAppend(
      FileBrowser *fb, guint8 **data, const gchar *desc
)
{
      gint i;
      FileBrowserIcon *icon;

      if((fb == NULL) || (data == NULL))
          return(NULL);

      i = MAX(fb->total_icons, 0);
      fb->total_icons = i + 1;
      fb->icon = (FileBrowserIcon **)g_realloc(
          fb->icon, fb->total_icons * sizeof(FileBrowserIcon *)
      );
      if(fb->icon == NULL)
      {
          fb->total_icons = 0;
          return(NULL);
      }

      fb->icon[i] = icon = FILE_BROWSER_ICON(g_malloc0(
          sizeof(FileBrowserIcon)
      ));
      if(icon != NULL)
      {
          icon->pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(&icon->mask, data);
          gdk_window_get_size(icon->pixmap, &icon->width, &icon->height);
          icon->desc = STRDUP(desc);
      }
      return(icon);
}


/*
 *    Returns the icon index appropriate for the object specified by
 *    full_path and lstat_buf.
 */
static gint FileBrowserMatchIconNumFromPath(
      FileBrowser *fb, const gchar *full_path, const struct stat *lstat_buf
)
{
      gint i, uid, gid;
      const gchar *ext, *name;
      mode_t m;

      if(fb == NULL)
          return(FB_ICON_FILE);

      if(full_path != NULL)
      {
          const gchar *s;

          name = g_basename(full_path);

#ifndef _WIN32
          /* Home directory? */
          s = fb->home_path;
          if((s != NULL) ? !strcmp(s, full_path) : FALSE)
            return(FB_ICON_FOLDER_HOME);

          /* Toplevel? */
          if(!strcmp(full_path, "/"))
            return(FB_ICON_DRIVE_ROOT);
#endif
          /* Drive path? */
          for(i = 0; i < fb->total_drive_paths; i++)
          {
            s = fb->drive_path[i];
            if((s != NULL) ? !strcmp(s, full_path) : FALSE)
                return(FB_ICON_DRIVE_FIXED);
          }

          /* Get extension (if any) */
          ext = strrchr(full_path, '.');
      }
      else
      {
          name = NULL;
          ext = NULL;
      }

      /* Get object's statistics */
      if(lstat_buf != NULL)
      {
          m = lstat_buf->st_mode;
          uid = lstat_buf->st_uid;
          gid = lstat_buf->st_gid;
      }
      else
      {
          m = 0;
          uid = 0;
          gid = 0;
      }

      /* Directory? */
#ifdef S_ISDIR
      if(S_ISDIR(m))
#else
      if(FALSE)
#endif
      {
#if defined(_WIN32)
          return(FB_ICON_FOLDER_CLOSED);
#else
          /* Accessable? */
          if(fb->euid != 0)
          {
            int accessable_icon = FB_ICON_FOLDER_CLOSED;

            /* Hidden? */
            if((name != NULL) ? (*name == '.') : FALSE)
            {
                if((name[2] != '\0') && (name[2] != '.'))
                  accessable_icon = FB_ICON_FOLDER_HIDDEN;
            }

            /* For non-root process id's we should check if the
             * process or the process' group owns the object and
             * respectively see if the object (the directory) is
             * executable (accessable) and return the appropriate
             * icon number.
             */
            /* This process owns object? */
            if(fb->euid == uid)
                return((m & S_IXUSR) ?
                  accessable_icon : FB_ICON_FOLDER_NOACCESS
                );
            /* This process' group id owns object? */
            else if(fb->egid == gid)
                return((m & S_IXGRP) ?
                  accessable_icon : FB_ICON_FOLDER_NOACCESS
                );
            /* Anonymous */
            else
                return((m & S_IXOTH) ?
                  accessable_icon : FB_ICON_FOLDER_NOACCESS
                );
          }
          else
          {
            /* Root always owns the object, so check if owner has
             * access
             */
            if(!(m & S_IXUSR))
                return(FB_ICON_FOLDER_NOACCESS);

            /* Hidden? */
            if((name != NULL) ? (*name == '.') : FALSE)
            {
                if((name[2] != '\0') && (name[2] != '.'))
                  return(FB_ICON_FOLDER_HIDDEN);
            }

            return(FB_ICON_FOLDER_CLOSED);
          }
#endif
      }

#if defined(_WIN32)
      if(ext != NULL)
      {
          if(!g_strcasecmp(ext, ".exe") ||
             !g_strcasecmp(ext, ".com") ||
             !g_strcasecmp(ext, ".bat")
          )
            return(FB_ICON_EXECUTABLE);
          else
            return(FB_ICON_FILE);
      }
      else
      {
          return(FB_ICON_FILE);
      }
#else
#ifdef S_ISLNK
      if(S_ISLNK(m))
          return(FB_ICON_LINK);
#endif
#ifdef S_ISFIFO
      if(S_ISFIFO(m))
          return(FB_ICON_PIPE);
#endif
#ifdef S_ISSOCK
      if(S_ISSOCK(m))
          return(FB_ICON_SOCKET);
#endif
#ifdef S_ISBLK
      if(S_ISBLK(m))
          return(FB_ICON_DEVICE_BLOCK);
#endif
#ifdef S_ISCHR
      if(S_ISCHR(m))
          return(FB_ICON_DEVICE_CHARACTER);
#endif
#if defined(S_IXUSR) && defined(S_IXGRP) && defined(S_IXOTH)
      if((m & S_IXUSR) || (m & S_IXGRP) || (m & S_IXOTH))
          return(FB_ICON_EXECUTABLE);
#endif

      /* Hidden? */
      if((name != NULL) ? (*name == '.') : FALSE)
      {
          if((name[2] != '\0') && (name[2] != '.'))
            return(FB_ICON_FILE_HIDDEN);
      }

      return(FB_ICON_FILE);
#endif
}


/*
 *    Returns the object at index i or NULL on error.
 */
static FileBrowserObject *FileBrowserGetObject(FileBrowser *fb, gint i)
{
      if(fb == NULL)
          return(NULL);

      if((i < 0) || (i >= fb->total_objects))
          return(NULL);
      else
          return(fb->object[i]);
}

/*
 *    Updates the Object's values.
 *
 *    The name and lstat_buf must be updated prior to this call since
 *    the information used to update the other values are based on
 *    them.
 *
 *    The updated values are; icon_num, width, and height.
 */
static void FileBrowserObjectUpdateValues(
      FileBrowser *fb, FileBrowserObject *o
)
{
      gint icon_num;
      GdkFont *font;
      GtkStyle *style;
      GtkWidget *w;
      FileBrowserIcon *icon;

      if((fb == NULL) || (o == NULL))
          return;

      /* Get the list widget's style and font */
      w = fb->list_da;
      style = (w != NULL) ? gtk_widget_get_style(w) : NULL;
      font = (style != NULL) ? style->font : NULL;

      /* Get icon number based on the object's path and statistics */
      o->icon_num = icon_num = FileBrowserMatchIconNumFromPath(
          fb, o->full_path, &o->lstat_buf
      );

      /* Get icon */
      icon = FileBrowserGetIcon(fb, icon_num);


      /* Set displayed_name from name, abbreviate it as needed */
      if(o->name != NULL)
      {
          const gchar *name = o->name;
          const gint    len = STRLEN(name),
                  max_chars = FB_LIST_ITEM_MAX_CHARS;

          g_free(o->displayed_name);
          if((len > max_chars) && (len > 3))
            o->displayed_name = g_strdup_printf(
                "...%s",
                name + (len - max_chars + 3)
            );
          else
            o->displayed_name = STRDUP(name);
      }
      else
      {
          g_free(o->displayed_name);
          o->displayed_name = STRDUP("");
      }


      /* Update size */
      if((o->displayed_name != NULL) && (font != NULL))
      {
          const gchar *name = o->displayed_name;
          const gint font_height = font->ascent + font->descent;
          GdkTextBounds b;
          gdk_string_bounds(font, name, &b);
          if(icon != NULL)
          {
            o->width = MAX(
                icon->width + FB_LIST_ICON_BORDER + 2 + b.width,
                1
            );
            o->height = MAX(
                icon->height,
                font_height
            );
          }
          else
          {
            o->width = MAX(
                b.width,
                1
            );
            o->height = MAX(
                font_height,
                1
            );
          }
      }
      else
      {
          if(icon != NULL)
          {
            o->width = icon->width;
            o->height = icon->height;
          }
          else
          {
            o->width = 1;
            o->height = 1;
          }
      }
}

/*
 *    Appends an object to the list.
 *
 *    Returns the new object or NULL on error.  The calling function
 *    needs to calculate the position.
 */
static FileBrowserObject *FileBrowserObjectAppend(
      FileBrowser *fb, const gchar *name, const gchar *full_path,
      struct stat *lstat_buf
)
{
      gint i;
      FileBrowserObject *o;

      if((fb == NULL) || (full_path == NULL))
          return(NULL);

      /* Allocate one more pointer for the array */
      i = MAX(fb->total_objects, 0);
      fb->total_objects = i + 1;
      fb->object = (FileBrowserObject **)g_realloc(
          fb->object, fb->total_objects * sizeof(FileBrowserObject *)
      );
      if(fb->object == NULL)
      {
          fb->total_objects = 0;
          return(NULL);
      }

      /* Create a new Object */
      fb->object[i] = o = FILE_BROWSER_OBJECT(g_malloc0(
          sizeof(FileBrowserObject)
      ));
      if(o != NULL)
      {
          o->name = STRDUP(name);
          o->full_path = STRDUP(full_path);
          if(lstat_buf != NULL)
            memcpy(&o->lstat_buf, lstat_buf, sizeof(struct stat));
          else
            memset(&o->lstat_buf, 0x00, sizeof(struct stat));

          /* Update values */
          FileBrowserObjectUpdateValues(fb, o);
      }

      return(o);
}

/*
 *    Updates the list.
 *
 *    Regets the list of objects from the current location and
 *    updates the list's scroll bar values
 */
static void FileBrowserListUpdate(FileBrowser *fb, const gchar *filter)
{
      gboolean    match_all_files,
                  show_hidden_objects;
      gint i;
      const gchar *cur_location, *ext;

      if(fb == NULL)
          return;

      FileBrowserSetBusy(fb, TRUE);

      show_hidden_objects = fb->show_hidden_objects;

      /* Get the extension from the filter (if specified) or from
       * the current file type
       */
      ext = (filter != NULL) ? filter : fb->cur_type.ext;
      if(ext != NULL)
          match_all_files = (!strcmp(ext, "*.*") || !strcmp(ext, "*")) ? TRUE : FALSE;
      else
          match_all_files = TRUE;

      /* Get listing of contents in current location */

      /* Delete current objects list and the selection */

      /* Unselect all */
      fb->focus_object = -1;
      g_list_free(fb->selection);
      fb->selection = fb->selection_end = NULL;

      /* Delete all objects */
      for(i = 0; i < fb->total_objects; i++)
          FileBrowserObjectDestroyCB(fb->object[i]);
      g_free(fb->object);
      fb->object = NULL;
      fb->total_objects = 0;  

      /* Reset objects per row */
      fb->objects_per_row = 0;


      /* Get the current location */
      cur_location = FileBrowserGetLocation(fb);
      if(cur_location != NULL)
      {
          /* Get the listing of objects in the current location */
          gint nobjs;
          gchar **names_list = GetDirEntNames2(cur_location, &nobjs);
          if(names_list != NULL)
          {
            struct stat stat_buf;
            const gchar *name;
            gchar *full_path;

            StringQSort(names_list, nobjs);

            /* Iterate through the names list and get only the
             * directories
             */
            for(i = 0; i < nobjs; i++)
            {
                name = names_list[i];
                if(name == NULL)
                  continue;

                /* Skip special directory notations */
                if(!strcmp(name, ".") ||
                   !strcmp(name, "..")
                )
                {
                  g_free(names_list[i]);
                  names_list[i] = NULL;
                  continue;
                }

                /* Skip hidden objects? */
                if(!show_hidden_objects)
                {
                  /* Does the name start with a '.' character? */
                  if(*name == '.')
                  {
                      g_free(names_list[i]);
                      names_list[i] = NULL;
                      continue;
                  }
                }

                /* Get the full path to the object */
                full_path = STRDUP(PrefixPaths(cur_location, name));
                if(full_path == NULL)
                {
                  g_free(names_list[i]);
                  names_list[i] = NULL;
                  continue;
                }

                /* Get this object's destination stats */
                if(stat(full_path, &stat_buf))
                {
                  g_free(full_path);
                  /* Do not delete this object from the names list */
                  continue;
                }

                /* Skip this object if it's destination is not
                 * a directory
                 */
                if(!S_ISDIR(stat_buf.st_mode))
                {
                  g_free(full_path);
                  /* Do not delete this object from the names list */
                  continue;
                }

                /* Get this directory's local stats */
#if !defined(_WIN32)
                if(lstat(full_path, &stat_buf))
                  memset(&stat_buf, 0x00, sizeof(struct stat));
#endif

                /* Append this directory and its stats to the list */
                FileBrowserObjectAppend(
                  fb, name, full_path, &stat_buf
                );

                g_free(full_path);

                /* Delete this directory from the names list */
                g_free(names_list[i]);
                names_list[i] = NULL;
            }

            /* Add all the other objects */
            for(i = 0; i < nobjs; i++)
            {
                name = names_list[i];
                if(name == NULL)
                  continue;

                /* Get the full path to the object */
                full_path = STRDUP(PrefixPaths(cur_location, name));
                if(full_path == NULL)
                {
                  g_free(names_list[i]);
                  names_list[i] = NULL;
                  continue;
                }

                /* Filter this object's name */
                if(!match_all_files)
                {
                  if(!FileBrowserObjectNameFilter(
                      name, full_path, ext
                  ))
                  {
                      g_free(full_path);
                      g_free(names_list[i]);
                      names_list[i] = NULL;
                      continue;
                  }
                }

                /* Get this object's local stats */
#if defined(_WIN32)
                if(stat(full_path, &stat_buf))
                  memset(&stat_buf, 0x00, sizeof(struct stat));
#else
                if(lstat(full_path, &stat_buf))
                  memset(&stat_buf, 0x00, sizeof(struct stat));
#endif

                /* Append this object and its stats to the list */
                FileBrowserObjectAppend(
                  fb, name, full_path, &stat_buf
                );

                g_free(full_path);

                /* Delete this object from the names list */
                g_free(names_list[i]);
                names_list[i] = NULL;
            }

            /* Delete the names list */
            g_free(names_list);
          }
      }

      /* Set the list's object positions and update the scroll
       * bar values
       */
      FileBrowserListUpdatePositions(fb);

      FileBrowserSetBusy(fb, FALSE);
}

/*
 *    Updates the list's object positions and scroll bar values.
 *
 *    This may produce a change in the list window's size due to
 *    the mapping or unmapping of the scroll bar, in which case
 *    a configure_event will be queued
 */
static void FileBrowserListUpdatePositions(FileBrowser *fb)
{
      const gint      border_major = 5,
                  border_minor = 2,
                  border_x = border_minor,
                  border_y = border_minor;
      gboolean    need_resize = FALSE;
      gint  i, width, height,
            total_objects, objects_per_row,
            cur_x = border_x,
            cur_y = border_y,
            longest_width = 0,
            last_longest_width = longest_width,
            list_x_max = 0, list_y_max = 0,
            list_x_inc = 0, list_x_page_inc = 0,
            list_y_inc = 0, list_y_page_inc = 0;
      GdkWindow *window;
      GtkWidget *w;
      FileBrowserListFormat list_format;
      FileBrowserObject *o = NULL;

      if(fb == NULL)
          return;

      list_format = fb->list_format;
      w = fb->list_da;
      if(w == NULL)
          return;

      window = w->window;
      if(window == NULL)
          return;

      /* Get the size of the list */
      gdk_window_get_size(window, &width, &height);

      /* Update the object positions and get values for the scroll
       * bar
       */
      switch(list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          total_objects = fb->total_objects;
          for(i = 0; i < total_objects; i++)
          {
              o = fb->object[i];
              if(o == NULL)
                continue;

            if(longest_width < o->width)
                last_longest_width = longest_width = o->width;

            o->x = cur_x;
            o->y = cur_y;
            cur_y += o->height + 1;
          }
          break;
        case FB_LIST_FORMAT_STANDARD:
          total_objects = fb->total_objects;
          objects_per_row = 0;
          for(i = 0; i < total_objects; i++)
          {
              o = fb->object[i];
              if(o == NULL)
                continue;

            if(longest_width < o->width)
                last_longest_width = longest_width = o->width;

            o->x = cur_x;
            o->y = cur_y;
            cur_y += o->height + 1;
            objects_per_row++;

            /* Maximum is 7 objects per "row" (column) */
            if(((cur_y + o->height) > height) ||
               (objects_per_row >= 7)
            )
            {
                if(objects_per_row > fb->objects_per_row)
                  fb->objects_per_row = objects_per_row;
                objects_per_row = 0;
                cur_y = border_y;
                cur_x += longest_width + border_major;
                longest_width = 0;
            }
          }
          break;
      }

      /* Use the last object to set the list's bounds, these
       * bounds will be used in updating of the scroll bar's values
       */
      if(o != NULL)
      {
          list_x_max = o->x + last_longest_width + border_x;
          list_y_max = o->y + o->height + border_y;
          list_x_inc = (gint)(width * 0.25);
          list_x_page_inc = (gint)(width * 0.5);
          list_y_inc = o->height;
          list_y_page_inc = (gint)(height * 0.5);
      }

      /* Show/hide the scroll bars */
      switch(list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          w = fb->list_hsb;
          if(w != NULL)
          {
            if(fb->hsb_map_state)
            {
                gtk_widget_hide(w);
                fb->hsb_map_state = FALSE;
                need_resize = TRUE;
            }
          }
          w = fb->list_vsb;
          if(w != NULL)
          {
            GtkRange *range = GTK_RANGE(w);
            GtkAdjustment *adj = range->adjustment;
            if(adj != NULL)
            {
                adj->lower = 0.0f;
                adj->upper = (gfloat)list_y_max;
                adj->value = adj->lower;
                adj->step_increment = (gfloat)list_y_inc;
                adj->page_increment = (gfloat)list_y_page_inc;
                adj->page_size = (gfloat)height;
                gtk_signal_emit_by_name(
                  GTK_OBJECT(adj), "changed"
                );
                gtk_signal_emit_by_name(
                  GTK_OBJECT(adj), "value_changed"
                );

                /* If content size is larger than visible size then
                 * map the scrollbar, otherwise unmap the scrollbar
                 * since it would not be needed.
                 */
                if((adj->upper - adj->lower) > adj->page_size)
                {
                  if(!fb->vsb_map_state)
                  {
                      gtk_widget_show(w);
                      fb->vsb_map_state = TRUE;
                      need_resize = TRUE;
                  }
                }
                else
                {
                  if(fb->vsb_map_state)
                  {
                      gtk_widget_hide(w);
                      fb->vsb_map_state = FALSE;
                      need_resize = TRUE;
                  }
                } 
            }
          }
          break;

        case FB_LIST_FORMAT_STANDARD:
          w = fb->list_vsb;
          if(w != NULL)
          {
            if(fb->vsb_map_state)
            {
                gtk_widget_hide(w);
                fb->vsb_map_state = FALSE;
                need_resize = TRUE;
            }
          }
          w = fb->list_hsb;
          if(w != NULL)
          {
            GtkRange *range = GTK_RANGE(w);
            GtkAdjustment *adj = range->adjustment;
            if(adj != NULL)
            {
                adj->lower = 0.0f;
                adj->upper = (gfloat)list_x_max;
                adj->value = adj->lower;
                adj->step_increment = (gfloat)list_x_inc;
                adj->page_increment = (gfloat)list_x_page_inc;
                adj->page_size = (gfloat)width;
                gtk_signal_emit_by_name(
                  GTK_OBJECT(adj), "changed"
                );
                gtk_signal_emit_by_name(
                  GTK_OBJECT(adj), "value_changed"
                );
                /* If content size is larger than visible size then
                 * map the scrollbar, otherwise unmap the scrollbar
                 * since it would not be needed
                 */
                if((adj->upper - adj->lower) > adj->page_size)
                {
                  if(!fb->hsb_map_state)
                  {
                      gtk_widget_show(w);
                      fb->hsb_map_state = TRUE;
                      need_resize = TRUE;
                  }
                }
                else
                {
                  if(fb->hsb_map_state)
                  {
                      gtk_widget_hide(w);
                      fb->hsb_map_state = FALSE;
                      need_resize = TRUE;
                  }
                }
            }
          }
          break;
      }

      /* Need to resize due to widgets being mapped or unmapped? */
      if(need_resize)
          gtk_widget_queue_resize(fb->toplevel);
}

/*
 *    Sets the DND icon for the object in the list.
 *
 *    The i specifies the object's index.
 */
static void FileBrowserListObjectSetDNDIcon(FileBrowser *fb, const gint i)
{
      FileBrowserIcon *icon;
      FileBrowserObject *o = FileBrowserGetObject(fb, i);
      if(fb == NULL)
          return;

      /* Get object's icon (if any) */
      icon = FileBrowserGetIcon(fb, o->icon_num);
      if(icon == NULL)
          return;

      /* Set new DND icon if it has a pixmap */
      if(icon->pixmap != NULL)
          GUIDNDSetDragIcon(
            icon->pixmap, icon->mask,
            icon->width / 2, icon->height / 2
          );
}

/*
 *    Returns the visibility of the object in the list.
 *
 *    The i specifies the object's index.
 *
 *    Returns one of GTK_VISIBILITY_*.
 */
static GtkVisibility FileBrowserListObjectVisibility(
      FileBrowser *fb, const gint i
)
{
      gint scroll_x = 0, scroll_y = 0;
      gint x, y, width, height;
      GdkWindow *window;
      GtkWidget *w;
      FileBrowserObject *o;

      if(fb == NULL)
          return(GTK_VISIBILITY_NONE);

      /* Get list's GdkWindow and its size, making sure it exists and
       * its size is positive
       */
      w = fb->list_da;
      window = (w != NULL) ? w->window : NULL;
      if(window == NULL)
          return(GTK_VISIBILITY_NONE);
      gdk_window_get_size(window, &width, &height);
      if((width <= 0) || (height <= 0))
          return(GTK_VISIBILITY_NONE);

      /* Get object */
      o = FileBrowserGetObject(fb, i);
      if(o == NULL)
          return(GTK_VISIBILITY_NONE);

      /* Get current scroll position */
      if(fb->list_hsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_hsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
      }
      if(fb->list_vsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_vsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
      }

      /* Check visibility by list display format */
      switch(fb->list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          y = o->y - scroll_y;
          if(((y + o->height) <= 0) || (y >= height))
            return(GTK_VISIBILITY_NONE);
          else if((y < 0) || ((y + o->height) > height))
            return(GTK_VISIBILITY_PARTIAL);
          else
            return(GTK_VISIBILITY_FULL);
          break;

        case FB_LIST_FORMAT_STANDARD:
          x = o->x - scroll_x;
          if(((x + o->width) <= 0) || (x >= width))
            return(GTK_VISIBILITY_NONE);
          else if((x < 0) || ((x + o->width) > width))
            return(GTK_VISIBILITY_PARTIAL);
          else
            return(GTK_VISIBILITY_FULL);
          break;
      }

      return(GTK_VISIBILITY_NONE);
}

/*
 *    Scrolls to the object in the list.
 *
 *    The i specifies the object's index.
 *
 *    The coefficient specifies the offset within the list window.
 */
static void FileBrowserListMoveToObject(
      FileBrowser *fb, const gint i, const gfloat coeff
)
{
      gint x, y, width, height;
      GdkWindow *window;
      GtkWidget *w;
      FileBrowserObject *o;

      if(fb == NULL)
          return;

      /* Get list's GdkWindow and its size, make sure that it exists
       * and its size is positive
       */
      w = fb->list_da;
      window = (w != NULL) ? w->window : NULL;
      if(window == NULL)
          return;
      gdk_window_get_size(window, &width, &height);
      if((width <= 0) || (height <= 0))
          return;

      /* Get object */
      o = FileBrowserGetObject(fb, i);
      if(o == NULL)
          return;

      /* Move to object depending on list display format */
      switch(fb->list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          y = o->y + (gint)(o->height * coeff);
          y -= (gint)(height * CLIP(coeff, 0.0f, 1.0f));
          if(fb->list_vsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_vsb);
            GtkAdjustment *adj = range->adjustment;
            if(adj != NULL)
                gtk_adjustment_set_value(
                  adj,
                  CLIP(
                      y,
                      adj->lower,
                      MAX(adj->upper - adj->page_size, adj->lower)
                  )
                );
          }
          break;

        case FB_LIST_FORMAT_STANDARD:
          x = o->x + (gint)(o->width * coeff);
          x -= (gint)(width * CLIP(coeff, 0.0f, 1.0f));
          if(fb->list_hsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_hsb);
            GtkAdjustment *adj = range->adjustment;
            if(adj != NULL)
                gtk_adjustment_set_value(
                  adj,
                  CLIP(
                      x,
                      adj->lower,
                      MAX(adj->upper - adj->page_size, adj->lower)
                  )
                );
          }
          break;
      }
}

/*
 *    Gets the object at the coordinates within the list.
 *
 *    The x and y specifies the coordinates in the list window.
 *
 *    Returns the object index or -1 on failed match.
 */
static gint FileBrowserListSelectCoordinates(
      FileBrowser *fb, const gint x, const gint y
)
{
      gint scroll_x = 0, scroll_y = 0;

      if(fb == NULL)
          return(-1);

      /* Get scroll position */
      if(fb->list_hsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_hsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
      }
      if(fb->list_vsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_vsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
      }

      /* Find object by list display format */
      switch(fb->list_format)
      {
          gint i, cx, cy;
          FileBrowserObject *o;

        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          for(i = 0; i < fb->total_objects; i++)
          {
            o = fb->object[i];
            if(o == NULL)
                continue;

            if((o->width <= 0) || (o->height <= 0))
                continue;

            /* Calculate object position with scrolled adjust */
            cx = o->x;
            cy = o->y - scroll_y;

            /* In bounds? */
            if((x >= cx) && (x < (cx + o->width)) &&
               (y >= cy) && (y < (cy + o->height))
            )
                return(i);
          }
          break;

        case FB_LIST_FORMAT_STANDARD:
          for(i = 0; i < fb->total_objects; i++)
          {
            o = fb->object[i];
            if(o == NULL)
                continue;

            if((o->width <= 0) || (o->height <= 0))
                continue;

            /* Calculate object position with scrolled adjust */
            cx = o->x - scroll_x;
            cy = o->y;

            /* In bounds? */
            if((x >= cx) && (x < (cx + o->width)) &&
               (y >= cy) && (y < (cy + o->height))
            )
                return(i);
          }
          break;
      }

      return(-1);
}

/*
 *    Redraws the File Browser's list header.
 */
static void FileBrowserListHeaderDraw(FileBrowser *fb)
{
      gint width, height, font_height;
      GdkFont *font;
      GdkDrawable *drawable;
      GdkWindow *window;
      GdkGC *gc;
      GtkStateType state;
      GtkStyle *style;
      GtkWidget *w = (fb != NULL) ? fb->list_header_da : NULL;
      if(w == NULL)
          return;

      if(!GTK_WIDGET_VISIBLE(w))
          return;

      window = w->window;
      if(window == NULL)
          return;

      drawable = (GdkDrawable *)fb->list_pm;
      if(drawable == NULL)
          drawable = window;

      state = GTK_WIDGET_STATE(w);
      style = gtk_widget_get_style(w);
      gdk_window_get_size(window, &width, &height);
      if((style == NULL) || (width <= 0) || (height <= 0))
          return;

      font = style->font;
      if(font == NULL)
          return;

      font_height = font->ascent + font->descent;


      /* Draw background */
      gc = style->bg_gc[state];
#if 0
      bg_pixmap = style->bg_pixmap[state];
      if(bg_pixmap != NULL)
      {
          gdk_gc_set_fill(gc, GDK_TILED);
          gdk_gc_set_tile(gc, bg_pixmap);
          gdk_gc_set_ts_origin(gc, 0, 0);
      }
      else
      {
          gdk_gc_set_fill(gc, GDK_SOLID);
          gdk_gc_set_foreground(gc, &style->bg[state]);
      }
#endif
      gdk_draw_rectangle(
          drawable, gc, TRUE,
          0, 0, width, height
      );
#if 0
      gdk_gc_set_fill(gc, GDK_SOLID);
      gdk_gc_set_tile(gc, NULL);
#endif


      /* Draw frame around entire header */
      gtk_draw_box(
          style, drawable, state,
          GTK_SHADOW_OUT,
          0, 0, width, height
      );

      /* Any column headings to draw? */
      if(fb->total_columns > 0)
      {
          gint i, last_column_position = 0;
          GdkRectangle rect;
          FileBrowserColumn *column;
          GdkGC *gc_text;

          /* Draw each column heading */
          for(i = 0; i < fb->total_columns; i++)
          {
            column = fb->column[i];
            if(column == NULL)
                continue;

            rect.x = last_column_position;
            rect.y = 0;
            rect.width = MAX(column->position - last_column_position, 0);
            rect.height = height;

            gtk_draw_box(
                style, drawable, state,
                GTK_SHADOW_OUT,
                rect.x, rect.y, rect.width, rect.height
            );

            gc_text = (column->flags & GTK_SENSITIVE) ?
                style->text_gc[state] : style->text_gc[GTK_STATE_INSENSITIVE];

            if((column->label != NULL) ? (*column->label != '\0') : FALSE)
            {
                const gchar *label = column->label;
                GdkTextBounds b;
                gdk_string_bounds(font, label, &b);
                gdk_gc_set_clip_origin(gc_text, 0, 0);
                gdk_gc_set_clip_rectangle(gc_text, &rect);
                switch(column->label_justify)
                {
                  case GTK_JUSTIFY_FILL:
                  case GTK_JUSTIFY_CENTER:
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + ((rect.width - b.width) / 2)) -
                        b.lbearing,
                      (rect.y + ((rect.height - font_height) / 2)) +
                        font->ascent,
                      label
                  );
                  break;
                  case GTK_JUSTIFY_RIGHT:
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + rect.width - b.width - 5) -
                        b.lbearing,
                      (rect.y + ((rect.height - font_height) / 2)) +
                        font->ascent,
                      label
                  );
                  break;
                  case GTK_JUSTIFY_LEFT:
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + 5) - b.lbearing,
                      (rect.y + ((rect.height - font_height) / 2)) +
                        font->ascent,
                      label
                  );
                  break;
                }
                gdk_gc_set_clip_rectangle(gc_text, NULL);
            }

            if((column->flags & GTK_SENSITIVE) &&
               (column->flags & GTK_HAS_FOCUS) &&
               GTK_WIDGET_HAS_FOCUS(w) &&
               GTK_WIDGET_SENSITIVE(w)
            )
                gdk_draw_rectangle(
                  drawable, style->fg_gc[state], FALSE,
                  rect.x, rect.y, rect.width - 1, rect.height - 1
                );

            last_column_position = column->position;
          }
      }

      /* Send drawable to window if drawable is not the window */
      if(drawable != (GdkDrawable *)window)
          gdk_draw_pixmap(
            window, style->fg_gc[state], drawable,
            0, 0, 0, 0, width, height
          );
}

/*
 *    Queues a redraw of the File Browser's list header.
 */
static void FileBrowserListHeaderQueueDraw(FileBrowser *fb)
{
      GtkWidget *w = (fb != NULL) ? fb->list_header_da : NULL;
      if(w != NULL)
          gtk_widget_queue_draw(w);
}

/*
 *    Redraws the File Browser's list.
 */
static void FileBrowserListDraw(FileBrowser *fb)
{
      gint width, height, font_height;
      gint scroll_x = 0, scroll_y = 0;
      GdkFont *font;
      GdkDrawable *drawable;
      GdkWindow *window;
      GdkGC *gc;
      GtkStateType state;
      GtkStyle *style;
      GtkWidget *w = (fb != NULL) ? fb->list_da : NULL;
      if(w == NULL)
          return;

      /* Do not draw if the list is not visible */
      if(!GTK_WIDGET_VISIBLE(w))
          return;

      window = w->window;
      if(window == NULL)
          return;

      drawable = (GdkDrawable *)fb->list_pm;
      if(drawable == NULL)
          drawable = window;

      state = GTK_WIDGET_STATE(w);
      style = gtk_widget_get_style(w);
      gdk_window_get_size(window, &width, &height);
      if((style == NULL) || (width <= 0) || (height <= 0))
          return;

      font = style->font;
      if(font == NULL)
          return;

      font_height = font->ascent + font->descent;


      /* Draw the background */
      gc = style->base_gc[state];
      gdk_draw_rectangle(
          drawable, gc, TRUE,
          0, 0, width, height
      );
#if 0
      if(style->bg_pixmap[state] != NULL)
          gtk_style_apply_default_background(
            style, drawable, FALSE, state,
            NULL,
            0, 0, width, height
          );
#endif

      /* Get the scroll position */
      if(fb->list_hsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_hsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
      }
      if(fb->list_vsb != NULL)
      {
          GtkRange *range = GTK_RANGE(fb->list_vsb);
          GtkAdjustment *adj = range->adjustment;
          scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
      }

      /* Begin drawing objects by list display format */
      switch(fb->list_format)
      {
          gboolean o_is_selected;
          GdkTextBounds b;
          gint i, x, y, icon_width;
          GdkGC *gc_text;
          const FileBrowserObject *o;
          const FileBrowserIcon *icon;

        case FB_LIST_FORMAT_VERTICAL_DETAILS:
          /* Make sure we have enough columns to draw for this
           * list display format
           */
          if(fb->total_columns >= 4)
          {
            mode_t m;
            const struct stat *lstat_buf;
            GdkRectangle rect;
            const FileBrowserColumn *column[4];

            /* Get the pointers to each column */
            for(i = 0; i < 4; i++)
                column[i] = fb->column[i];

            /* Draw each object */
            for(i = 0; i < fb->total_objects; i++)
            {
                o = fb->object[i];
                if(o == NULL)
                  continue;

                if((o->width <= 0) || (o->height <= 0))
                  continue;

                x = o->x;
                y = o->y - scroll_y;

                /* Is this object completely off the y axis? */
                if(((y + o->height) < 0) || (y >= height))
                  continue;

                /* Get this object's values */
                lstat_buf = &o->lstat_buf;
                m = lstat_buf->st_mode;

                /* Get calculate this object's boundary
                 * rectangle
                 */
                rect.x = x - 0;
                rect.y = y;
                rect.width = MIN(o->width, column[0]->position - 0);
                rect.height = o->height;

                /* Is this object selected? */
                o_is_selected = OBJISSEL(fb, i);
                if(o_is_selected)
                {
                  /* Get the selection graphic contexts and
                   * draw the selection background
                   */
                  gc = style->fg_gc[GTK_STATE_SELECTED];
                  gc_text = style->text_gc[GTK_STATE_SELECTED];
                  gdk_draw_rectangle(
                      drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
                      rect.x, rect.y, rect.width, rect.height
                  );
                }
                else
                {
                  /* Get the graphic contexts for drawing
                   * this object
                   */
                  gc = style->fg_gc[state];
                  gc_text = style->text_gc[state];
                }

                /* Draw the icon */
                icon = FileBrowserGetIcon(fb, o->icon_num);
                if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
                {
                  GdkBitmap *mask = icon->mask;
                  GdkPixmap *pixmap = icon->pixmap;
                  gdk_gc_set_clip_origin(gc, x, y);
                  gdk_gc_set_clip_mask(gc, mask);
                  gdk_draw_pixmap(
                      drawable, gc, pixmap,
                      0, 0, x, y, icon->width, icon->height
                  );
                  gdk_gc_set_clip_mask(gc, NULL);
                  icon_width = icon->width;
                }
                else
                {
                  icon_width = 0;
                }

                /* Set up the clip rectangle for drawing of the
                 * object's name cell
                 */
                gdk_gc_set_clip_origin(gc, 0, 0);
                gdk_gc_set_clip_rectangle(gc, &rect);
                gdk_gc_set_clip_origin(gc_text, 0, 0);
                gdk_gc_set_clip_rectangle(gc_text, &rect);

                /* Draw the name */
                if(o->displayed_name != NULL)
                {
                  const gchar *s = o->displayed_name;
                  gdk_string_bounds(font, s, &b);
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (x + icon_width + FB_LIST_ICON_BORDER) -
                        b.lbearing,
                      (y + ((o->height - font_height) / 2)) +
                        font->ascent,
                      s
                  );
                }
                else
                {
                  /* No name, but we still need to have font extents
                   * for use with drawing the subsequent cells below
                   */
                  gdk_string_bounds(font, "X", &b);
                }

                /* If the object was selected it means we drew the
                 * icon and name with the selected gc's. We should
                 * now restore the gc's and use the current state
                 * gc's for drawing subsequent values
                 */
                if(o_is_selected)
                {
                  gdk_gc_set_clip_rectangle(gc, NULL);
                  gdk_gc_set_clip_rectangle(gc_text, NULL);
                  gc = style->fg_gc[state];
                  gc_text = style->text_gc[state];
                }

                /* Draw the size */
                if(S_ISREG(m))
                {
                  gchar s[80];
                  g_snprintf(
                      s, sizeof(s),
                      "%ld",
                      lstat_buf->st_size
                  );
                  gdk_string_bounds(font, s, &b);
                  rect.x = column[0]->position;
                  rect.width = column[1]->position - column[0]->position;
                  if(rect.width > 0)
                      gdk_gc_set_clip_rectangle(gc_text, &rect);
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + rect.width - b.width - 2) -
                        b.lbearing,
                      (y + (o->height / 2) -
                        ((font->ascent + font->descent) / 2)) +
                        font->ascent,
                      s
                  );
                }

                /* Draw the permissions */
#ifdef S_ISLNK
                if(!S_ISLNK(m))
#else
                if(TRUE)
#endif
                {
                  gchar s[80];
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
                  g_snprintf(
                      s, sizeof(s),
                      "%c%c%c%c%c%c%c%c%c",
                      (m & S_IRUSR) ? 'r' : '-',
                      (m & S_IWUSR) ? 'w' : '-',
                      (m & S_ISUID) ? 'S' :
                        ((m & S_IXUSR) ? 'x' : '-'),
                      (m & S_IRGRP) ? 'r' : '-',
                      (m & S_IWGRP) ? 'w' : '-',
                      (m & S_ISGID) ? 'G' :
                        ((m & S_IXGRP) ? 'x' : '-'),
                      (m & S_IROTH) ? 'r' : '-',
                      (m & S_IWOTH) ? 'w' : '-',
                      (m & S_ISVTX) ? 'T' :
                        ((m & S_IXOTH) ? 'x' : '-')
                  );
#else
                  strcpy(s, "rwxrwxrwx");
#endif
                  gdk_string_bounds(font, s, &b);
                  rect.x = column[1]->position;
                  rect.width = column[2]->position - column[1]->position;
                  if(rect.width > 0)
                      gdk_gc_set_clip_rectangle(gc_text, &rect);
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + 2) - b.lbearing,
                      (y + (o->height / 2) -
                        ((font->ascent + font->descent) / 2)) +
                        font->ascent,
                      s
                  );
                }

                /* Draw the modified time */
                if(S_ISREG(m))
                {
                  time_t mtime = lstat_buf->st_mtime;
                  gchar *s = STRDUP((mtime > 0) ? ctime(&mtime) : "*undefined*");
                  gchar *s2 = strchr(s, '\n');
                  if(s2 == NULL)
                      s2 = strchr(s, '\r');
                  if(s2 != NULL)
                      *s2 = '\0';
                  gdk_string_bounds(font, s, &b);
                  rect.x = column[2]->position;
                  rect.width = column[3]->position - column[2]->position;
                  if(rect.width > 0)
                      gdk_gc_set_clip_rectangle(gc_text, &rect);
                  gdk_draw_string(
                      drawable, font, gc_text,
                      (rect.x + 2) - b.lbearing,
                      (y + (o->height / 2) -
                        ((font->ascent + font->descent) / 2)) +
                        font->ascent,
                      s
                  );
                  g_free(s);
                }

                /* Restore the graphic context */
                gdk_gc_set_clip_rectangle(gc_text, NULL);
                gdk_gc_set_clip_rectangle(gc, NULL);

                /* Draw the focus rectangle around this object? */
                if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
                {
                  GdkGCValues gcv;
                  gdk_gc_get_values(gc, &gcv);
                  gdk_gc_set_function(gc, GDK_INVERT);
                  rect.x = x - 0;
                  rect.width = MIN(o->width, column[0]->position - 0);
                  gdk_draw_rectangle(
                      drawable, gc, FALSE,
                      rect.x, rect.y,
                      rect.width - 1, rect.height - 1
                  );
                  gdk_gc_set_function(gc, gcv.function);
                }
            }
          }
          break;

        case FB_LIST_FORMAT_VERTICAL:
          for(i = 0; i < fb->total_objects; i++)
          {
            o = fb->object[i];
            if(o == NULL)
                continue;

            if((o->width <= 0) || (o->height <= 0))
                continue;

            x = o->x;
            y = o->y - scroll_y;

            /* Is this object completely off the y axis? */
            if(((y + o->height) < 0) || (y >= height))
                continue;

            /* Is object selected? */
            o_is_selected = OBJISSEL(fb, i);
            if(o_is_selected)
            {
                /* Get the selection graphic contexts and draw
                 * the selection background
                 */
                gc = style->fg_gc[GTK_STATE_SELECTED];
                gc_text = style->text_gc[GTK_STATE_SELECTED];
                gdk_draw_rectangle(
                  drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
                  x, y, o->width, o->height
                );
            }
            else
            {
                gc = style->fg_gc[state];
                gc_text = style->text_gc[state];
            }

            /* Draw the icon */
            icon = FileBrowserGetIcon(fb, o->icon_num);
            if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
            {
                GdkBitmap *mask = icon->mask;
                GdkPixmap *pixmap = icon->pixmap;
                gdk_gc_set_clip_origin(gc, x, y);
                gdk_gc_set_clip_mask(gc, mask);
                gdk_draw_pixmap(
                  drawable, gc, pixmap,
                  0, 0, x, y, icon->width, icon->height
                );
                gdk_gc_set_clip_mask(gc, NULL);
                icon_width = icon->width;
            }
            else
            {
                icon_width = 0;
            }

            /* Draw the name */
            if(o->displayed_name != NULL)
            {
                const gchar *s = o->displayed_name;
                gdk_string_bounds(font, s, &b);
                gdk_draw_string(
                  drawable, font, gc_text,
                  (x + icon_width + FB_LIST_ICON_BORDER) -
                      b.lbearing,
                  (y + ((o->height - font_height) / 2)) +
                      font->ascent,
                  s
                );
            }

            /* Draw the focus rectangle around this object? */
            if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
            {
                GdkGCValues gcv;
                gdk_gc_get_values(gc, &gcv);
                gdk_gc_set_function(gc, GDK_INVERT);
                gdk_draw_rectangle(
                  drawable, gc, FALSE,
                  x, y, o->width - 1, o->height - 1
                );
                gdk_gc_set_function(gc, gcv.function);
            }
          }
          break;

        case FB_LIST_FORMAT_STANDARD:
          /* Draw each object */
          for(i = 0; i < fb->total_objects; i++)
          {
            o = fb->object[i];
            if(o == NULL)
                continue;

            if((o->width <= 0) || (o->height <= 0))
                continue;

            x = o->x - scroll_x;
            y = o->y;

            /* Is this object completely off the x axis? */
            if(((x + o->width) < 0) || (x >= width))
                continue;

            /* Is this object selected? */
            o_is_selected = OBJISSEL(fb, i);
            if(o_is_selected)
            {
                /* Get the selection graphic contexts and draw
                 * the selection background
                 */
                gc = style->fg_gc[GTK_STATE_SELECTED];
                gc_text = style->text_gc[GTK_STATE_SELECTED];
                gdk_draw_rectangle(
                  drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
                  x, y, o->width, o->height
                );
            }
            else
            {
                gc = style->fg_gc[state];
                gc_text = style->text_gc[state];
            }

            /* Draw the icon */
            icon = FileBrowserGetIcon(fb, o->icon_num);
            if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
            {
                GdkBitmap *mask = icon->mask;
                GdkPixmap *pixmap = icon->pixmap;
                gdk_gc_set_clip_origin(gc, x, y);
                gdk_gc_set_clip_mask(gc, mask);
                gdk_draw_pixmap(
                  drawable, gc, pixmap,
                  0, 0, x, y, icon->width, icon->height
                );
                gdk_gc_set_clip_mask(gc, NULL);
                icon_width = icon->width;
            }
            else
            {
                icon_width = 0;
            }

            /* Draw the name */
            if(o->displayed_name != NULL)
            {
                const gchar *s = o->displayed_name;
                gdk_string_bounds(font, s, &b);
                gdk_draw_string(
                  drawable, font, gc_text,
                  (x + icon_width + FB_LIST_ICON_BORDER) -
                      b.lbearing,
                  (y + ((o->height - font_height) / 2)) +
                      font->ascent,
                  s
                );
            }

            /* Draw the focus rectangle around this object? */
            if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
            {
                GdkGCValues gcv;
                gdk_gc_get_values(gc, &gcv);
                gdk_gc_set_function(gc, GDK_INVERT);
                gdk_draw_rectangle(
                  drawable, gc, FALSE,
                  x, y, o->width - 1, o->height - 1
                );
                gdk_gc_set_function(gc, gcv.function);
            }
          }
          break;
      }

#if 0
      /* Draw focus rectangle if widget is in focus */
      if(GTK_WIDGET_HAS_FOCUS(w) && (state != GTK_STATE_INSENSITIVE))
#if defined(_WIN32)
          gdk_draw_rectangle(
            drawable, style->fg_gc[state], FALSE,
            0, 0, width - 1, height - 1
          );
#else
          gtk_draw_focus(
            style, drawable,
            0, 0, width - 1, height - 1
          );
#endif
#endif

      /* Send drawable to window if drawable is not the window */
      if(drawable != (GdkDrawable *)window)
          gdk_draw_pixmap(
            window, style->fg_gc[state], drawable,
            0, 0, 0, 0, width, height
          );
}

/*
 *    Queues a redraw of the File Browser's list.
 */
static void FileBrowserListQueueDraw(FileBrowser *fb)
{
      GtkWidget *w = (fb != NULL) ? fb->list_da : NULL;
      if(w != NULL)
          gtk_widget_queue_draw(w);
}


/*
 *    Returns the list column at index i.
 */
static FileBrowserColumn *FileBrowserListGetColumn(FileBrowser *fb, gint i)
{
      if(fb == NULL)
          return(NULL);
      if((i < 0) || (i >= fb->total_columns))
          return(NULL);
      else
          return(fb->column[i]);
}

/*
 *    Appends a new list column.
 */
static FileBrowserColumn *FileBrowserListColumnAppend(
      FileBrowser *fb, const gchar *label, const gint width
)
{
      gint i;
      FileBrowserColumn *column, *column_prev;

      if(fb == NULL)
          return(NULL);

      i = fb->total_columns;
      fb->total_columns = i + 1;
      fb->column = (FileBrowserColumn **)g_realloc(
          fb->column, fb->total_columns * sizeof(FileBrowserColumn *)
      );
      if(fb->column == NULL)
      {
          fb->total_columns = 0;
          return(NULL);
      }

      fb->column[i] = column = FILE_BROWSER_COLUMN(g_malloc0(
          sizeof(FileBrowserColumn)
      ));
      if(column == NULL)
          return(NULL);

      column->label = STRDUP(label);
      column_prev = FileBrowserListGetColumn(fb, i - 1);
      column->position = ((column_prev != NULL) ?
          column_prev->position : 0) + width;
      column->label_justify = GTK_JUSTIFY_LEFT;
      column->flags = GTK_SENSITIVE | GTK_CAN_FOCUS |
                  GTK_CAN_DEFAULT;

      column->drag = FALSE;
      column->drag_position = column->position;
      column->drag_last_drawn_position = column->drag_position;

      return(column);
}

/*
 *    Deletes all list columns.
 */
static void FileBrowserListColumnsClear(FileBrowser *fb)
{
      gint i;

      if(fb == NULL)
          return;

      for(i = 0; i < fb->total_columns; i++)
          FileBrowserColumnDestroyCB(fb->column[i]);
      g_free(fb->column);
      fb->column = NULL;
      fb->total_columns = 0;
}


/*
 *    Updates the entry with the selected objects, if there are no
 *    selected objects then the entry is blanked.
 */
static void FileBrowserEntrySetSelectedObjects(FileBrowser *fb)
{
      gchar *s;
      GList *glist;
      const FileBrowserObject *o;
      GtkEntry *entry = (fb != NULL) ? (GtkEntry *)fb->entry : NULL;
      if(entry == NULL)
          return;

      /* Iterate through selected objects and generate a string s to
       * be set as the new entry value
       */
      s = STRDUP("");
      for(glist = fb->selection;
          glist != NULL;
          glist = g_list_next(glist)
      )
      {
          o = FileBrowserGetObject(fb, (gint)glist->data);
          if((o != NULL) ? (o->name != NULL) : FALSE)
          {
            s = strinsstr(s, -1, o->name);

            /* Add deliminator to string if there is another
             * object after this one
             */
            if(g_list_next(glist) != NULL)
                s = strinschr(s, -1, ',');
          }
      }

      /* Update entry value if there were objects or else clear it */
      gtk_entry_set_text(entry, (s != NULL) ? s : "");
      gtk_entry_set_position(entry, -1);

      g_free(s);
}


/*
 *    Sets the types list.
 *
 *    The list and total specifies the new types list.
 */
static void FileBrowserTypesListSetTypes(
        FileBrowser *fb,
      fb_type_struct **list, const gint total
)
{
      gint i, n;
      gchar *label;
      pulist_struct *pulist;
      pulistbox_struct *pulistbox;
      fb_type_struct *t, *t2;

      if(fb == NULL)
          return;

      pulistbox = fb->types_pulistbox;
      pulist = PUListBoxGetPUList(pulistbox);
      if(pulist == NULL)
          return;

      fb->freeze_count++;

      /* Delete all the types in the list */
      PUListClear(pulist);

      /* Set the new types list */
      for(i = 0; i < total; i++) 
      {
          t = list[i];
          if(t == NULL)
            continue;

          if(t->ext == NULL)
            continue;

          t2 = FB_TYPE(g_malloc0(sizeof(fb_type_struct)));
          if(t2 == NULL)
            continue;

          t2->ext = STRDUP(t->ext);
          t2->name = STRDUP(t->name);

          if(STRISEMPTY(t2->name))
            label = STRDUP(t2->ext);
          else
            label = g_strdup_printf(
                "%s (%s)", t2->name, t2->ext
            );
          n = PUListAddItem(pulist, label);
          g_free(label);
          if(n < 0)
          {
            FileBrowserTypeDelete(t2);
            continue;
          }

          PUListSetItemDataFull(
            pulist, n,
            t2, FileBrowserTypesPUListItemDestroyCB
          );
      }

      /* Make sure that no more than 10 items are visible */
      n = PUListGetTotalItems(pulist);
      PUListBoxSetLinesVisible(
          fb->types_pulistbox,
          CLIP(n, 1, 10)
      );

      /* Select the first item */
      PUListBoxSelect(pulistbox, 0);
      FileBrowserTypesPUListChangedCB(pulistbox, 0, fb);

      fb->freeze_count--;
}


/*
 *    Locations popup list item destroy callback.
 */
static void FileBrowserLocationsPUListItemDestroyCB(gpointer data)
{
#if 0
      gchar *full_path = (gchar *)data;
      if(full_path == NULL)
          return;
#endif
      g_free(data);
}

/*
 *    Types popup list item destroy callback.
 */
static void FileBrowserTypesPUListItemDestroyCB(gpointer data)
{
      FileBrowserTypeDelete(FB_TYPE(data));
}

/*
 *    File browser icon destroy callback.
 */
static void FileBrowserIconDestroyCB(gpointer data)
{
      FileBrowserIcon *icon = FILE_BROWSER_ICON(data);
      if(icon == NULL)
          return;

      GDK_PIXMAP_UNREF(icon->pixmap);
      GDK_BITMAP_UNREF(icon->mask);
      g_free(icon->desc);
      g_free(icon);
}

/*
 *    File browser object destroy callback.
 */
static void FileBrowserObjectDestroyCB(gpointer data)
{
      FileBrowserObject *o = FILE_BROWSER_OBJECT(data);
      if(o == NULL)
          return;

      g_free(o->displayed_name);
      g_free(o->full_path);
      g_free(o->name);
      g_free(o);
}

/*
 *    File browser list column destroy callback.
 */
static void FileBrowserColumnDestroyCB(gpointer data)
{
      FileBrowserColumn *column = FILE_BROWSER_COLUMN(data);
      if(column == NULL)
          return;

      g_free(column->label);
      g_free(column);
}

/*
 *    File browser ok button signal callback.
 */
static void FileBrowserOKCB(GtkWidget *widget, gpointer data)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      if(FPromptIsQuery())
      {
          FPromptBreakQuery();
      }
      else if(PUListIsQuery(PUListBoxGetPUList(fb->loc_pulistbox)))
      {
          PUListBreakQuery(PUListBoxGetPUList(fb->loc_pulistbox));
      }
      else if(PUListIsQuery(PUListBoxGetPUList(fb->types_pulistbox)))
      {
          PUListBreakQuery(PUListBoxGetPUList(fb->types_pulistbox));
      }
      else if(CDialogIsQuery())
      {

      }
      else
      {
          gint i;
          GtkWidget *w;

          /* Mark that user response was OK */
          fb->user_response = TRUE;

          /* Delete previous list of selected paths */
          for(i = 0; i < fb->total_selected_paths; i++)
            g_free(fb->selected_path[i]);
          g_free(fb->selected_path);
          fb->selected_path = NULL;
          fb->total_selected_paths = 0;

          /* Get value in entry and explode it to the list of
           * return paths
           */
          w = fb->entry;
          if(w != NULL)
          {
            const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
            if(!STRISEMPTY(s))
            {
                /* Explode the entry's string value at all ','
                 * characters and then prefix the current location
                 * to each exploded string
                 */
                gint i, n;
                const gchar *cur_location = FileBrowserGetLocation(fb);
                gchar   *name,
                        **names_list = g_strsplit(s, ",", -1);
                for(i = 0; names_list[i] != NULL; i++)
                {
                  name = names_list[i];
/* Do not strip the spaces in the object's name since spaces may,
 * in fact, be part of the object's name
 */
/*                strstrip(name); */

                  /* Append a new path to the selected_path
                   * array, the new path will be either name
                   * (if name is an absolute path) or the
                   * cur_location prefixed to name (if name is
                   * not an absolute path).
                   */
                  n = MAX(fb->total_selected_paths, 0);
                  fb->total_selected_paths = n + 1;
                  fb->selected_path = (gchar **)g_realloc(
                      fb->selected_path,
                      fb->total_selected_paths * sizeof(gchar *)
                  );
                  if(fb->selected_path != NULL)
                  {
                      /* If name is an absolute path then copy
                       * it to the selected_path list
                       *
                       * Otherwise copy the cur_location prefixed
                       * to name
                       */
                      if(g_path_is_absolute(name))
                        fb->selected_path[n] = STRDUP(name);
                      else
                        fb->selected_path[n] = STRDUP(PrefixPaths(
                            cur_location, name
                        ));
                  }
                  else
                  {
                      fb->total_selected_paths = 0;
                  }

                  g_free(name);
                }
                g_free(names_list);
              }
              else
              {
                /* File name entry is empty, so set the current
                 * location as the return path.
                 */
                const gchar *cur_location = FileBrowserGetLocation(fb);
                gint n = MAX(fb->total_selected_paths, 0);
                fb->total_selected_paths = n + 1;
                fb->selected_path = (gchar **)g_realloc(
                    fb->selected_path,
                    fb->total_selected_paths * sizeof(gchar *)
                );
                if(fb->selected_path == NULL)
                {
                  fb->total_selected_paths = 0;
                }
                else
                {
                    fb->selected_path[n] = STRDUP(cur_location);
                }
              }
          }

          /* Unmap */
          FileBrowserUnmap();

          /* Break out of blocking loop */
          if(fb->block_loop_level > 0)
          {
            gtk_main_quit();
            fb->block_loop_level--;
          }
      }
}

/*
 *    File browser cancel button signal callback.
 */
static void FileBrowserCancelCB(GtkWidget *widget, gpointer data)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      if(FPromptIsQuery())
      {
          FPromptBreakQuery();
      }
      else if(PUListIsQuery(PUListBoxGetPUList(fb->loc_pulistbox)))
      {
          PUListBreakQuery(PUListBoxGetPUList(fb->loc_pulistbox));
      }
      else if(PUListIsQuery(PUListBoxGetPUList(fb->types_pulistbox)))
      {
          PUListBreakQuery(PUListBoxGetPUList(fb->types_pulistbox));
      }
      else if(CDialogIsQuery())
      {

      }
      else
      {
          /* Unmap */
          FileBrowserUnmap();

          /* Break out of blocking loop */
          if(fb->block_loop_level > 0)
          {
            gtk_main_quit();
            fb->block_loop_level--;
          }
      }
}

/*
 *    Toplevel GtkWindow "delete_event" signal callback.
 */
static gint FileBrowserDeleteEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
)
{
      FileBrowserCancelCB(widget, data);
      return(TRUE);
}


/*
 *    Locations popup list "changed" signal callback.
 */
static void FileBrowserLocPUListChangedCB(
      pulistbox_struct *pulistbox, gint i, gpointer data
)
{
      const gchar *location;
      pulist_struct *pulist;
      FileBrowser *fb = FILE_BROWSER(data);
      if((pulistbox == NULL) || (fb == NULL))
          return;

      if(fb->freeze_count > 0)
          return;

      pulist = PUListBoxGetPUList(pulistbox);
      if(pulist == NULL)
          return;

      location = (const gchar *)PUListGetItemData(
          pulist, i
      );
      if(location == NULL)
          return;

      fb->freeze_count++;

      FileBrowserSetLocation(fb, location);

      fb->freeze_count--;
}


/*
 *    List header event signal callback.
 */
static gint FileBrowserListHeaderEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
)
{
      gint status = FALSE;
      GdkEventConfigure *configure;
      GdkEventExpose *expose;
      GdkEventFocus *focus;
      GdkEventButton *button;
      GdkEventMotion *motion;
      GtkWidget *w;
      FileBrowser *fb = FILE_BROWSER(data);
      if((event == NULL) || (fb == NULL))
          return(status);

      w = fb->list_header_da;
      if(w == NULL)
          return(status);

      /* List widget must also be valid */
      if(fb->list_da == NULL)
          return(status);

#define DRAW_DRAG_LINE(x)                             \
{ if(column != NULL) {                                \
 gint line_p = (gint)(x);                             \
 GdkWindow *window1, *window2;                              \
 GdkGC *gc;                                     \
 GtkStyle *style;                               \
                                                \
 window1 = w->window;                                 \
 window2 = fb->list_da->window;                             \
 style = gtk_widget_get_style(w);                     \
 gc = (style != NULL) ?                               \
  style->fg_gc[GTK_WIDGET_STATE(w)] : NULL;                 \
                                                \
 if((window1 != NULL) && (window2 != NULL) && (gc != NULL)) {     \
  GdkGCValues gcv;                                    \
  gint width, height;                                 \
                                                \
  gdk_gc_get_values(gc, &gcv);                              \
  gdk_gc_set_function(gc, GDK_INVERT);                      \
                                                \
  /* Draw drag line on window1 */                     \
  gdk_window_get_size(window1, &width, &height);            \
  gdk_draw_line(window1, gc, line_p, 0, line_p, height);    \
                                                \
  /* Draw drag line on window2 */                     \
  gdk_window_get_size(window2, &width, &height);            \
  gdk_draw_line(window2, gc, line_p, 0, line_p, height);    \
                                                \
  gdk_gc_set_function(gc, gcv.function);              \
 }                                              \
} }

      switch((gint)event->type)
      {
        case GDK_CONFIGURE:
          configure = (GdkEventConfigure *)event;
          status = TRUE;
          break;

        case GDK_EXPOSE:
          expose = (GdkEventExpose *)event;
          FileBrowserListHeaderDraw(fb);
          status = TRUE;
          break;

        case GDK_FOCUS_CHANGE:
          focus = (GdkEventFocus *)event;
          if(focus->in && !GTK_WIDGET_HAS_FOCUS(w))
          {
            GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
            FileBrowserListHeaderQueueDraw(fb);
          }
          else if(!focus->in && GTK_WIDGET_HAS_FOCUS(w))
          {
            GTK_WIDGET_UNSET_FLAGS(w, GTK_HAS_FOCUS);
            FileBrowserListHeaderQueueDraw(fb);
          }
          status = TRUE;
          break;

        case GDK_BUTTON_PRESS:
          button = (GdkEventButton *)event;
          if(!GTK_WIDGET_HAS_FOCUS(w))
            gtk_widget_grab_focus(w);
          if(fb->total_columns > 0)
          {
            gint i, cp, tolor = 3;
            gint p = (gint)button->x;
            FileBrowserColumn *column;

#define COLUMN_POSITION(p)    (((p) != NULL) ? (p)->position : 0)

            /* Iterate through all columns to update focus and
             * reset drag state.
             */
            for(i = 0; i < fb->total_columns; i++)
            {
                column = fb->column[i];
                if(column == NULL)
                  continue;

                /* Get left edge column position */
                cp = ((i - 1) >= 0) ?
                  COLUMN_POSITION(fb->column[i - 1]) : 0;

                if((p >= (cp + tolor)) && (p < (column->position - tolor)))
                  column->flags |= GTK_HAS_FOCUS;
                else
                  column->flags &= ~GTK_HAS_FOCUS;

                column->drag = FALSE;
            }
            FileBrowserListHeaderQueueDraw(fb);

            /* Iterate through all columns, checking for one
             * where the pointer is over its resizing area.
             */
            for(i = fb->total_columns - 1; i >= 0; i--)
            {
                column = fb->column[i];
                if(column == NULL)
                  continue;

                cp = column->position;
                if((p >= (cp - tolor)) && (p < (cp + (2 * tolor))))
                {
                  /* Update column drag values */
                  column->drag = TRUE;
                  column->drag_position = cp;

                  /* Draw drag line on list header and list */
                  DRAW_DRAG_LINE(cp);
                  column->drag_last_drawn_position =
                      column->drag_position;

                  break;
                }
            }
#undef COLUMN_POSITION
          }
          status = TRUE;
          break;

        case GDK_BUTTON_RELEASE:
          button = (GdkEventButton *)event;
          if(fb->total_columns > 0)
          {
            gint i, pos_shift_delta = 0;
            FileBrowserColumn *column;

            /* Iterate through all columns, checking for one that
             * is being dragged and set that new column's
             * position
             */
            for(i = 0; i < fb->total_columns; i++)
            {
                column = fb->column[i];
                if(column == NULL)
                  continue;

                /* This column being dragged? */
                if(column->drag)
                {
                  column->drag = FALSE;
                  pos_shift_delta = column->drag_position -
                      column->position;
                  column->position = column->drag_position;
                }
                else
                {
                  column->position += pos_shift_delta;
                }
            }

            /* Redraw the list header and the list */
            FileBrowserListHeaderQueueDraw(fb);
            FileBrowserListQueueDraw(fb);
          }
          status = TRUE;
          break;

        case GDK_MOTION_NOTIFY:
          motion = (GdkEventMotion *)event;
          if(fb->total_columns > 0)
          {
            gint i, tolor = 3, left_column_pos = 0;
            GdkCursor *cursor = NULL;
            gint p = (gint)motion->x;
            FileBrowserColumn *column;


            /* Iterate through all columns and check if one is
             * being dragged (resized) in which case it will be
             * handled accordingly.  Also if a pointer has
             * moved into the dragging area of a column then
             * the new cursor will be specified.
             */
            for(i = 0; i < fb->total_columns; i++)
            {
                column = fb->column[i];
                if(column == NULL)
                  continue;

                /* This column being dragged? */
                if(column->drag)
                {
                  column->drag_position = CLIP(
                      p, left_column_pos, w->allocation.width
                  );

                  /* Draw drag line on list header and list */
                  DRAW_DRAG_LINE(column->drag_last_drawn_position);
                  DRAW_DRAG_LINE(column->drag_position);
                  column->drag_last_drawn_position =
                      column->drag_position;

                  /* Update cursor just in case */
                  cursor = fb->cur_column_hresize;

                  /* No need to handle other columns after
                   * this one since it should be the only
                   * being dragged.
                   */
                  break;
                }
                else
                {
                  /* Column not being dragged, check if the
                   * pointer has moved into the dragging
                   * area of this column.
                   */
                  gint cp = column->position;
                  if((p >= (cp - tolor)) && (p < (cp + (2 * tolor))))
                  {
                      cursor = fb->cur_column_hresize;
                  }

                  left_column_pos = column->position;
                }
            }
            gdk_window_set_cursor(w->window, cursor);
            gdk_flush();
          }
          status = TRUE;
          break;

        case GDK_LEAVE_NOTIFY:
          gdk_window_set_cursor(w->window, NULL);
          gdk_flush();
          status = TRUE;
          break;
      }

#undef DRAW_DRAG_LINE

      return(status);
}

/*
 *    List GtkDrawingArea event signal callback.
 */
static gint FileBrowserListEventCB(
      GtkWidget *widget, GdkEvent *event, gpointer data
)
{
      gint status = FALSE;
      gboolean key_press;
      GdkEventConfigure *configure;
      GdkEventExpose *expose;
      GdkEventFocus *focus;
      GdkEventKey *key;
      GdkEventButton *button;
      GdkEventMotion *motion;
      GtkWidget *w;
      FileBrowser *fb = FILE_BROWSER(data);
      if((event == NULL) || (fb == NULL))
          return(status);

      w = fb->list_da;
      if(w == NULL)
          return(status);

      switch((gint)event->type)
      {
        case GDK_CONFIGURE:
          configure = (GdkEventConfigure *)event;
          /* Recreate the list's GdkPixmap buffer */
          GDK_PIXMAP_UNREF(fb->list_pm);
          if((configure->width > 0) && (configure->height > 0))
            fb->list_pm = gdk_pixmap_new(
                configure->window,
                configure->width, configure->height,
                -1
            );
          else
            fb->list_pm = NULL;
          /* Update the list's object positions */
          FileBrowserListUpdatePositions(fb);
          status = TRUE;
          break;

        case GDK_EXPOSE:
          expose = (GdkEventExpose *)event;
          FileBrowserListDraw(fb);
          status = TRUE;
          break;

        case GDK_FOCUS_CHANGE:
          focus = (GdkEventFocus *)event;
          if(focus->in && !GTK_WIDGET_HAS_FOCUS(w))
          {
            GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
            FileBrowserListQueueDraw(fb);
          }
          else if(!focus->in && GTK_WIDGET_HAS_FOCUS(w))
          {
            GTK_WIDGET_UNSET_FLAGS(w, GTK_HAS_FOCUS);
            FileBrowserListQueueDraw(fb);
          }
          status = TRUE;
          break;

        case GDK_KEY_PRESS:
        case GDK_KEY_RELEASE:
          key = (GdkEventKey *)event;
          key_press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
#define STOP_KEY_SIGNAL_EMIT  {           \
 if(widget != NULL)                       \
  gtk_signal_emit_stop_by_name(                 \
   GTK_OBJECT(widget),                    \
   key_press ?                            \
    "key_press_event" : "key_release_event"     \
  );                                \
}
          /* First handle key event by list format */
          switch(fb->list_format)
          {
            /* Vertical List Format */
            case FB_LIST_FORMAT_VERTICAL_DETAILS:
            case FB_LIST_FORMAT_VERTICAL:
            switch(key->keyval)
            {
              case GDK_Up:          /* Change Focus Up */
              case GDK_KP_Up:
              case GDK_Left:
              case GDK_KP_Left:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->value - adj->step_increment,
                              adj->lower
                            )
                        );
                  }
                  else if(fb->focus_object > 0)
                  {
                      gint    n = fb->focus_object,
                              i = n - 1;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        if(!OBJISSEL(fb, n))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)n
                            );
                        if(!OBJISSEL(fb, i))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)i
                            );
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT                
                status = TRUE;
                break;

              case GDK_Down:  /* Change Focus Down */
              case GDK_KP_Down:
              case GDK_Right:
              case GDK_KP_Right:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MIN(
                              adj->value + adj->step_increment,
                              MAX(adj->upper - adj->page_size, adj->lower)
                            )
                        );
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      gint    n = fb->focus_object,
                              i = n + 1;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        if(!OBJISSEL(fb, n))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)n
                            );
                        if(!OBJISSEL(fb, i))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)i
                            );
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT
                status = TRUE;
                break;

              case GDK_Page_Up:     /* Page Focus Up */
              case GDK_KP_Page_Up:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->value - adj->page_increment,
                              adj->lower
                            )
                          );
                  }
                  else
                  {
                      const FileBrowserObject *o = (fb->total_objects > 0) ?
                        fb->object[0] : NULL;
                      gint i, n = fb->focus_object,
                        row_height = (o != NULL) ?
                            (o->height + 1) : 0;
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;

                      if((row_height > 0) && (adj != NULL))
                      {
                        fb->focus_object = i = MAX(
                            fb->focus_object -
                            (gint)(adj->page_increment / row_height),
                            0
                        );

                        /* Select? */
                        if((key->state & GDK_SHIFT_MASK) &&
                           (fb->total_objects > 0)
                        )
                        {
                            gint j;
                            for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
                            {
                              if(!OBJISSEL(fb, j))
                                  fb->selection = g_list_append(
                                    fb->selection, (gpointer)j
                                  );
                            }
                            fb->selection_end = g_list_last(fb->selection);
                            FileBrowserEntrySetSelectedObjects(fb);
                        }

                        /* Scroll if focus object is not fully visible */
                        if(FileBrowserListObjectVisibility(fb, i) !=
                            GTK_VISIBILITY_FULL
                        )
                            FileBrowserListMoveToObject(fb, i, 0.0f);
                        else
                            FileBrowserListQueueDraw(fb);
                      }
                  }
                }
                status = TRUE;
                break;

              case GDK_Page_Down:   /* Page Focus Down */
              case GDK_KP_Page_Down:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MIN(
                              adj->value + adj->page_increment,
                              MAX(adj->upper - adj->page_size, adj->lower)
                            )
                          );
                  }
                  else
                  {
                      const FileBrowserObject *o = (fb->total_objects > 0) ?
                        fb->object[0] : NULL;
                      gint i, n = fb->focus_object,
                        row_height = (o != NULL) ?
                            (o->height + 1) : 0;
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;

                      if((row_height > 0) && (adj != NULL))
                      {
                        fb->focus_object = i = MIN(
                            fb->focus_object +
                            (gint)(adj->page_increment / row_height),
                            fb->total_objects - 1
                        );

                        /* Select? */
                        if((key->state & GDK_SHIFT_MASK) &&
                           (fb->total_objects > 0)
                        )
                        {
                            gint j;
                            for(j = MAX(n, 0); j <= i; j++)
                            {
                              if(!OBJISSEL(fb, j))
                                  fb->selection = g_list_append(
                                    fb->selection, (gpointer)j
                                  );
                            }
                            fb->selection_end = g_list_last(fb->selection);
                            FileBrowserEntrySetSelectedObjects(fb);
                        }

                        /* Scroll if focus object is not fully visible */
                        if(FileBrowserListObjectVisibility(fb, i) !=
                            GTK_VISIBILITY_FULL
                        )
                            FileBrowserListMoveToObject(fb, i, 1.0f);
                        else
                            FileBrowserListQueueDraw(fb);
                      }
                  }
                }
                status = TRUE;
                break;

              case GDK_Home:  /* Focus Beginning */
              case GDK_KP_Home:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            adj->lower
                        );
                  }
                  else if(fb->focus_object > 0)
                  {
                      gint    i = 0,
                              n = fb->focus_object;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;

              case GDK_End:         /* Focus End */
              case GDK_KP_End:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_vsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->upper - adj->page_size,
                              adj->lower
                            )
                          );
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      gint    i = MAX(fb->total_objects - 1, 0),
                              n = fb->focus_object;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MAX(n, 0); j <= i; j++)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 1.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;
            }
            break;

            /* Horizontal List Format */
            case FB_LIST_FORMAT_STANDARD:
            switch(key->keyval)
            {
              case GDK_Up:          /* Focus Up */
              case GDK_KP_Up:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      /* No vertical scrolling */
                  }
                  else if(fb->focus_object > 0)
                  {
                      const gint    n = fb->focus_object,
                              i = n - 1;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        if(!OBJISSEL(fb, n))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)n
                            );
                        if(!OBJISSEL(fb, i))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)i
                            );
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT
                status = TRUE;
                break;

              case GDK_Down:  /* Focus Down */
              case GDK_KP_Down:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      /* No vertical scrolling */
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      const gint    n = MAX(fb->focus_object, 0),
                              i = n + 1;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        if(!OBJISSEL(fb, n))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)n
                            );
                        if(!OBJISSEL(fb, i))
                            fb->selection = g_list_append(
                              fb->selection, (gpointer)i
                            );
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT
                status = TRUE;
                break;

              case GDK_Left:  /* Focus Left */
              case GDK_KP_Left:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->value - adj->step_increment,
                              adj->lower
                            )
                        );
                  }
                  else if(fb->focus_object > 0)
                  {
                      const gint    n = fb->focus_object,
                              i = MAX(
                  fb->focus_object - fb->objects_per_row, 0
                              );

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                          GTK_VISIBILITY_FULL
                      )
                          FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                          FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT
                status = TRUE;
                break;

              case GDK_Right: /* Focus Right */
              case GDK_KP_Right:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                        range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MIN(
                              adj->value + adj->step_increment,
                              MAX(adj->upper - adj->page_size, adj->lower)
                            )
                        );
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      const gint    n = MAX(fb->focus_object, 0),
                              i = MIN(
                                  n + fb->objects_per_row,
                                  fb->total_objects - 1
                              );

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = n; j <= i; j++)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                          GTK_VISIBILITY_FULL
                      )
                          FileBrowserListMoveToObject(fb, i, 0.5f);
                      else
                          FileBrowserListQueueDraw(fb);
                  }
                }
                STOP_KEY_SIGNAL_EMIT
                status = TRUE;
                break;

              case GDK_Page_Up:     /* Page Focus Left */
              case GDK_KP_Page_Up:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->value - adj->page_increment,
                              adj->lower
                            )
                          );
                  }
                  else if(fb->focus_object > 0)
                  {
                      gint        n = fb->focus_object,
                              i = MAX(
                  fb->focus_object - fb->objects_per_row, 0
                              );

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;

              case GDK_Page_Down:   /* Page Focus Right */
              case GDK_KP_Page_Down:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MIN(
                              adj->value + adj->page_increment,
                              MAX(adj->upper - adj->page_size, adj->lower)
                            )
                          );
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      gint        n = fb->focus_object,
                              i = MIN(
                      fb->focus_object + fb->objects_per_row,
                      fb->total_objects - 1
                              );

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MAX(n, 0); j <= i; j++)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 1.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;

              case GDK_Home:  /* Focus Beginning */
              case GDK_KP_Home:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            adj->lower
                        );
                  }
                  else if(fb->focus_object > 0)
                  {
                      gint        i = 0,
                              n = fb->focus_object;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 0.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;

              case GDK_End:         /* Focus End */
              case GDK_KP_End:
                if(key_press)
                {
                  /* Scroll only? */
                  if(key->state & GDK_CONTROL_MASK)
                  {
                      GtkRange *range = (GtkRange *)fb->list_hsb;
                      GtkAdjustment *adj = (range != NULL) ?
                          range->adjustment : NULL;
                      if(adj != NULL)
                        gtk_adjustment_set_value(
                            adj,
                            MAX(
                              adj->upper - adj->page_size,
                              adj->lower
                            )
                          );
                  }
                  else if(fb->focus_object < (fb->total_objects - 1))
                  {
                      gint        i = MAX(fb->total_objects - 1, 0),
                              n = fb->focus_object;

                      fb->focus_object = i;

                      /* Select? */
                      if((key->state & GDK_SHIFT_MASK) &&
                         (fb->total_objects > 0)
                      )
                      {
                        gint j;
                        for(j = MAX(n, 0); j <= i; j++)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                        }
                        fb->selection_end = g_list_last(fb->selection);
                        FileBrowserEntrySetSelectedObjects(fb);
                      }

                      /* Scroll if focus object is not fully visible */
                      if(FileBrowserListObjectVisibility(fb, i) !=
                        GTK_VISIBILITY_FULL
                      )
                        FileBrowserListMoveToObject(fb, i, 1.0f);
                      else
                        FileBrowserListQueueDraw(fb);
                  }
                }
                status = TRUE;
                break;
            }
            break;
          }
          /* If the key event was not handled above then we
           * handle it generally independent of the list
           * format here
           */
          if(!status)
          {
            switch(key->keyval)
            {
              case GDK_Return:      /* Enter */
              case GDK_KP_Enter:
              case GDK_3270_Enter:
              case GDK_ISO_Enter:
                if(key_press)
                  FileBrowserEntryEnterCB(fb->entry, fb);
                status = TRUE;
                break;

              case GDK_space: /* Toggle Select */
                if(key_press)
                {
                  gint i = fb->focus_object;
                  if(OBJISSEL(fb, i))
                      fb->selection = g_list_remove(
                        fb->selection, (gpointer)i
                      );
                  else
                      fb->selection = g_list_append(
                        fb->selection, (gpointer)i
                      );
                  fb->selection_end = g_list_last(fb->selection);
                  FileBrowserListQueueDraw(fb);
                  FileBrowserEntrySetSelectedObjects(fb);
                }
                status = TRUE;
                break;

              case GDK_BackSpace:
                if(key_press)
                  FileBrowserGoToParentCB(NULL, fb);
                status = TRUE;
                break;

              case GDK_F5:    /* Refresh */
                if(key_press)
                  FileBrowserRefreshCB(NULL, fb);
                status = TRUE;
                break;

              case GDK_Insert:      /* New Directory */
#if 0
/* Some other accelerator seems to catch this even with this widget in
 * focus so we don't need to respond to it.
 */
                if(key_press)
                  FileBrowserNewDirectoryCB(NULL, fb);
#endif
                status = TRUE;
                break;

              case GDK_F2:          /* Rename */
                if(key_press)
                  FileBrowserRenameCB(NULL, fb);
                status = TRUE;
                break;

              case GDK_Delete:      /* Delete */
                if(key_press)
                  FileBrowserDeleteCB(NULL, fb);
                status = TRUE;
                break;
#if 0
/* Some other accelerator seems to catch this even with this widget in
 * focus so we don't need to respond to it.
 */
              case 'a':       /* Select All */
                if(key_press && (key->state & GDK_CONTROL_MASK))
                  FileBrowserSelectAllCB(NULL, fb);
                status = TRUE;
                break;

              case 'u':       /* Unelect All */
                if(key_press && (key->state & GDK_CONTROL_MASK))
                  FileBrowserUnselectAllCB(NULL, fb);
                status = TRUE;
                break;

              case 'i':       /* Invert Selection */
                if(key_press && (key->state & GDK_CONTROL_MASK))
                  FileBrowserInvertSelectionCB(NULL, fb);
                status = TRUE;
                break;
#endif
            }
          }
#undef STOP_KEY_SIGNAL_EMIT
          break;

        case GDK_BUTTON_PRESS:
          button = (GdkEventButton *)event;
          /* Unmap the floating prompt as needed */
          if(FPromptIsQuery())
            FPromptBreakQuery();
          /* Grab focus as needed */
          if(!GTK_WIDGET_HAS_FOCUS(w))
            gtk_widget_grab_focus(w);
          /* Handle by button number */
          switch(button->button)
          {
            case GDK_BUTTON1:       /* Select */
            /* Select one item at a time? */
            if(button->state & GDK_CONTROL_MASK)
            {
                const gint i = FileBrowserListSelectCoordinates(
                  fb, (gint)button->x, (gint)button->y
                );
                if(i > -1)
                {
                  /* If new object is already selected then unselect
                   * it, otherwise add it to the selection
                   */
                  fb->last_single_select_object = -1;
                  fb->focus_object = i;
                  if(OBJISSEL(fb, i))
                  {
                      fb->selection = g_list_remove(
                        fb->selection, (gpointer)i
                      );
                      fb->selection_end = g_list_last(fb->selection);
                  }
                  else
                  {
                      fb->selection = g_list_append(
                        fb->selection, (gpointer)i
                      );
                      fb->selection_end = g_list_last(fb->selection);

                      /* Update DND icon based on the newly selected
                       * object.
                       */
                      FileBrowserListObjectSetDNDIcon(fb, i);
                  }

                  /* If matched object is not fully visibile then
                   * scroll so that it is in the center of the
                   * list.
                   */
                  if(FileBrowserListObjectVisibility(fb, i) !=
                      GTK_VISIBILITY_FULL
                  )
                      FileBrowserListMoveToObject(fb, i, 0.5f);
                }
            }
            /* Select a group of items */
            else if(button->state & GDK_SHIFT_MASK)
            {
                const gint i = FileBrowserListSelectCoordinates(
                  fb, (gint)button->x, (gint)button->y
                );
                if(i > -1)
                {
                  /* Select all objects between the last selected
                   * object and the current one
                   */
                  fb->last_single_select_object = -1;
                  fb->focus_object = i;
                  if(fb->selection_end != NULL)
                  {
                      const gint n = (gint)fb->selection_end->data;
                      if(i > n)
                      {
                        gint j = n + 1;
                        while(j <= i)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                            j++;
                        }
                        fb->selection_end = g_list_last(fb->selection);
                      }
                      else if(i < n)
                      {
                        gint j = n - 1;
                        while(j >= i)
                        {
                            if(!OBJISSEL(fb, j))
                              fb->selection = g_list_append(
                                  fb->selection, (gpointer)j
                              );
                            j--;
                        }
                        fb->selection_end = g_list_last(fb->selection);
                      }
                  }
                  else
                  {
                      /* No previously selected object, so just
                       * select this one
                       */
                      fb->selection = g_list_append(
                        fb->selection, (gpointer)i
                      );
                      fb->selection_end = g_list_last(fb->selection);
                  }

                  /* Update the DND icon based on the newly
                   * selected object
                   */
                  FileBrowserListObjectSetDNDIcon(fb, i);

                  /* If matched object is not fully visibile
                   * then scroll so that it is in the center
                   * of the list
                   */
                  if(FileBrowserListObjectVisibility(fb, i) !=
                      GTK_VISIBILITY_FULL
                  )
                      FileBrowserListMoveToObject(fb, i, 0.5f);
                }
            }
            /* Single select */
            else
            {
                const gint i = FileBrowserListSelectCoordinates(
                  fb, (gint)button->x, (gint)button->y
                );
                if(i > -1)
                {
                  /* If the last single selected object (if any)
                   * is the same as the newly selected object
                   * then update last_single_select_object
                   */
                  if((g_list_length(fb->selection) == 1) ?
                      ((gint)fb->selection->data == i) : FALSE
                  )
                      fb->last_single_select_object = i;
                  else
                      fb->last_single_select_object = -1;

                  /* Unselect all */
                  fb->focus_object = -1;
                  g_list_free(fb->selection);
                  fb->selection = fb->selection_end = NULL;

                  /* Select this object */
                  fb->focus_object = i;
                  fb->selection = g_list_append(
                      fb->selection, (gpointer)i
                  );
                  fb->selection_end = g_list_last(fb->selection);

                  /* Update DND icon based on the newly selected
                   * object
                   */
                  FileBrowserListObjectSetDNDIcon(fb, i);

                  /* If matched object is not fully visibile then
                   * scroll so that it is in the center of the
                   * list
                   */
                  if(FileBrowserListObjectVisibility(fb, i) !=
                      GTK_VISIBILITY_FULL
                  )
                      FileBrowserListMoveToObject(fb, i, 0.5f);
                }
                else
                {
                  /* Unselect all */
                  fb->focus_object = -1;
                  g_list_free(fb->selection);
                  fb->selection = fb->selection_end = NULL;
                }
            }
            /* Update entry with the new list of selected
             * objects
             */
            FileBrowserEntrySetSelectedObjects(fb);
            break;

            case GDK_BUTTON2:
            /* Start scrolling */
            gdk_pointer_grab(
                button->window,
                FALSE,
                GDK_BUTTON_PRESS_MASK |
                  GDK_BUTTON_RELEASE_MASK |
                  GDK_POINTER_MOTION_MASK |
                  GDK_POINTER_MOTION_HINT_MASK,
                NULL,
                fb->cur_translate,
                button->time
            );
            break;

            case GDK_BUTTON3:
            /* Right-click menu */
            if(fb->list_menu != NULL)
            {
                GtkMenu *menu = GTK_MENU(fb->list_menu);
                gint i = FileBrowserListSelectCoordinates(
                  fb, (gint)button->x, (gint)button->y
                );
                /* If ctrl or shift modifier keys are held then do
                 * not modify selection before mapping of menu
                 */
                if((button->state & GDK_CONTROL_MASK) ||
                   (button->state & GDK_SHIFT_MASK)
                )
                {
                  /* Do not modify selection, just update focus
                   * object
                   */
                  fb->focus_object = i;
                }
                else if(i > -1)
                {
                  /* Unselect all objects */
                  fb->focus_object = -1;
                  if(fb->selection != NULL)
                      g_list_free(fb->selection);
                  fb->selection = fb->selection_end = NULL;

                  /* Select this object */
                  fb->focus_object = i;
                  fb->selection = g_list_append(
                      fb->selection, (gpointer)i
                  );
                  fb->selection_end = g_list_last(fb->selection);

                  /* Update DND icon based on the newly selected
                   * object
                   */
                  FileBrowserListObjectSetDNDIcon(fb, i);
                }

                /* Map menu */
                gtk_menu_popup(
                  menu, NULL, NULL,
                  NULL, NULL,
                  button->button, button->time
                );
            }
            break;

            case GDK_BUTTON4:
            /* Scroll left? */
            if(fb->list_format == FB_LIST_FORMAT_STANDARD)
            {
                GtkRange *range = (GtkRange *)fb->list_hsb;
                GtkAdjustment *adj = (range != NULL) ?
                  range->adjustment : NULL;
                if(adj != NULL)
                {
                  const gfloat inc = MAX(
                      (0.25f * adj->page_size),
                      adj->step_increment
                  );
                  gtk_adjustment_set_value(
                      adj,
                      MAX(
                        adj->value - inc,
                        adj->lower
                      )
                  );
                }
            }
            /* Scroll up */
            else
            {
                GtkRange *range = (GtkRange *)fb->list_vsb;
                GtkAdjustment *adj = (range != NULL) ?
                  range->adjustment : NULL;
                if(adj != NULL)
                {
                  const gfloat inc = MAX(
                      (0.25f * adj->page_size),
                      adj->step_increment
                  );
                  gtk_adjustment_set_value(
                      adj,
                      MAX(
                        adj->value - inc,
                        adj->lower
                      )
                  );
                }
            }
            break;

            case GDK_BUTTON5:
            /* Scroll Right? */
            if(fb->list_format == FB_LIST_FORMAT_STANDARD)
            {
                GtkRange *range = (GtkRange *)fb->list_hsb;
                GtkAdjustment *adj = (range != NULL) ?
                  range->adjustment : NULL;
                if(adj != NULL)
                {
                  const gfloat inc = MAX(
                      (0.25f * adj->page_size),
                      adj->step_increment
                  );
                  gtk_adjustment_set_value(
                      adj,
                      MIN(
                        adj->value + inc,
                        MAX(adj->upper - adj->page_size, adj->lower)
                      )
                  );
                }
            }
            /* Scroll down */
            else
            {
                GtkRange *range = (GtkRange *)fb->list_vsb;
                GtkAdjustment *adj = (range != NULL) ?
                  range->adjustment : NULL;
                if(adj != NULL)
                {
                  const gfloat inc = MAX(
                      (0.25f * adj->page_size),
                      adj->step_increment
                  );
                  gtk_adjustment_set_value(
                      adj,
                      MIN(
                        adj->value + inc,
                        MAX(adj->upper - adj->page_size, adj->lower)
                      )
                  );
                }
            }
            break;
          }
          fb->button = button->button;
          fb->drag_last_x = (gint)button->x;
          fb->drag_last_y = (gint)button->y;
          FileBrowserListQueueDraw(fb);
          status = TRUE;
          break;

        case GDK_BUTTON_RELEASE:
          button = (GdkEventButton *)event;
          /* Handle by button number */
          switch(button->button)
          {
            case GDK_BUTTON1:
            if(!(button->state & GDK_CONTROL_MASK) &&
               !(button->state & GDK_SHIFT_MASK)
            )
            {
                gulong  double_click_int = 800,
                        last_time = fb->last_button1_release_time,
                        cur_time = (gulong)button->time,
                        dt = (
                  (last_time > 0) && (cur_time > last_time)
                        ) ? (cur_time - last_time) : 0l;
#if 0
                /* Slow double click? */
                if((dt > double_click_int) &&
                   (dt <= (2 * double_click_int))
                   (dt != 0)
                )
                {
                  /* Last single selected object did not change? */
                  if(fb->last_single_select_object > -1)
                  {
                      /* Call rename callback */
                      FileBrowserRenameCB(NULL, fb);
                  }
                }
#endif
                /* Double click? */
                if((dt <= double_click_int) && (dt != 0))
                {
                  /* Last single selected object did not change? */
                  if(fb->last_single_select_object > -1)
                  {
                      /* Call entry enter callback */
                      FileBrowserEntryEnterCB(fb->entry, fb);
                  }
                }
            }
            fb->last_button1_release_time = (gulong)button->time;
            break;

            case GDK_BUTTON2:
            /* Stop scrolling as needed */
            if(gdk_pointer_is_grabbed())
                gdk_pointer_ungrab(button->time);
            break;
          }
          fb->button = 0;
          status = TRUE;
          break;

        case GDK_MOTION_NOTIFY:
          motion = (GdkEventMotion *)event;
#define STOP_SIGNAL_EMIT      {                 \
 gtk_signal_emit_stop_by_name(                        \
  GTK_OBJECT(widget), "motion_notify_event"           \
 );                                       \
}
          switch(fb->button)
          {
            gint dx, dy;
            GtkAdjustment *adj;
            GtkRange *range;

            case GDK_BUTTON1:
            if(motion->is_hint)
            {
                gint x, y;
                GdkModifierType mask;
                gdk_window_get_pointer(
                  motion->window, &x, &y, &mask
                );
            }
            break;

            case GDK_BUTTON2:
            if(motion->is_hint)
            {
                gint x, y;
                GdkModifierType mask;
                gdk_window_get_pointer(
                  motion->window, &x, &y, &mask
                );
                dx = x - fb->drag_last_x;
                dy = y - fb->drag_last_y;
                fb->drag_last_x = x;
                fb->drag_last_y = y;
            }
            else
            {
                dx = (gint)(motion->x - fb->drag_last_x);
                dy = (gint)(motion->y - fb->drag_last_y);
                fb->drag_last_x = (gint)motion->x;
                fb->drag_last_y = (gint)motion->y;
            }

            range = (GtkRange *)fb->list_hsb;
            adj = (range != NULL) ? range->adjustment : NULL;
            if((adj != NULL) && (dx != 0) &&
               (fb->list_format == FB_LIST_FORMAT_STANDARD)
            )
            {
                if((adj->upper - adj->page_size) > adj->lower)
                  gtk_adjustment_set_value(
                      adj,
                      CLIP(
                        adj->value - dx,
                        adj->lower, adj->upper - adj->page_size
                      )
                  );
            }

            range = (GtkRange *)fb->list_vsb;
            adj = (range != NULL) ? range->adjustment : NULL;
            if((adj != NULL) && (dy != 0) &&
               ((fb->list_format == FB_LIST_FORMAT_VERTICAL) ||
                (fb->list_format == FB_LIST_FORMAT_VERTICAL_DETAILS)
               )
            )
            {
                if((adj->upper - adj->page_size) > adj->lower)
                  gtk_adjustment_set_value(
                      adj,
                      CLIP(
                        adj->value - dy,
                        adj->lower, adj->upper - adj->page_size
                      )
                  );
            }
            STOP_SIGNAL_EMIT
            status = TRUE;
            break;
          }
#undef STOP_SIGNAL_EMIT
          break;
      }

      return(status);
}

/*
 *    List scrollbar GtkAdjustment "value_changed" signal callback.
 */
static void FileBrowserListScrollCB(
      GtkAdjustment *adj, gpointer data
)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      FileBrowserListQueueDraw(fb);
}

/*
 *    Select all callback.
 */
static void FileBrowserSelectAllCB(GtkWidget *widget, gpointer data)
{
      gint i;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      if(fb->selection != NULL)
          g_list_free(fb->selection);
      fb->selection = NULL;
      for(i = 0; i < fb->total_objects; i++)
          fb->selection = g_list_append(
            fb->selection, (gpointer)i
          );
      fb->selection_end = g_list_last(fb->selection);
      FileBrowserListQueueDraw(fb);
      FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *    Unselect all callback.
 */
static void FileBrowserUnselectAllCB(GtkWidget *widget, gpointer data)
{
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      if(fb->selection != NULL)
          g_list_free(fb->selection);
      fb->selection = fb->selection_end = NULL;
      FileBrowserListQueueDraw(fb);
      FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *    Invert selection callback.
 */
static void FileBrowserInvertSelectionCB(GtkWidget *widget, gpointer data)
{
      gint i;
      GList *glist = NULL;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      for(i = 0; i < fb->total_objects; i++)
      {
          if(!OBJISSEL(fb, i))
            glist = g_list_append(glist, (gpointer)i);
      }
      if(fb->selection != NULL)
          g_list_free(fb->selection);
      fb->selection = glist;
      fb->selection_end = g_list_last(fb->selection);
      FileBrowserListQueueDraw(fb);
      FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *    Used by FileBrowserRenameCB() as the apply callback for
 *    the floating prompt.
 */
static void FileBrowserRenameFPromptCB(
      gpointer data, const gchar *value
)
{
      gint i;
      GList *glist;
      FileBrowserObject *o;
      FileBrowser *fb = FILE_BROWSER(data);
      if((fb == NULL) || STRISEMPTY(value))
          return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_RENAME_FAILED     "Reagrupe Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_RENAME_FAILED     "Renommer Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_RENAME_FAILED     "Benennen Sie Versagt Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_RENAME_FAILED     "Rinominare Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_RENAME_FAILED     "Herdoop Geverzuimenene"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_RENAME_FAILED     "O Rename Fracassou"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_RENAME_FAILED     "Ombenevn Failed"
#else
# define TITLE_RENAME_FAILED     "Rename Failed"
#endif

#define MESSAGE_RENAME_FAILED(s)    {     \
 CDialogSetTransientFor(fb->toplevel);          \
 CDialogGetResponse(                      \
  TITLE_RENAME_FAILED,                    \
  (s), NULL,                              \
  CDIALOG_ICON_WARNING,                   \
  CDIALOG_BTNFLAG_OK,                     \
  CDIALOG_BTNFLAG_OK                      \
 );                                 \
 CDialogSetTransientFor(NULL);                  \
}

      /* No directory deliminators may exist in the new value */
      if(strchr(value, G_DIR_SEPARATOR) != NULL)
      {
          gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"El nombre nuevo \"%s\" contiene deliminators de\n\
guía de '%c' que no se permiten en un nombre de objeto."
#elif defined(PROG_LANGUAGE_FRENCH)
"Le nouveau \"%s\" de nom contient deliminators d'annuaire\n\
de '%c' qui ne sont pas permis dans un nom de l'objet."
#elif defined(PROG_LANGUAGE_GERMAN)
"Der neu \"%s\" enthält '%c' Verzeichnis deliminators, das\n\
im Namen eines Objekts nicht erlaubt wird."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Lo \"%s\" di nome nuovo contiene il deliminators\n\
di elenco di '%c' che non sono lasciati in un nome dell'oggetto."
#elif defined(PROG_LANGUAGE_DUTCH)
"De nieuwe naam \"%s\" bevat '%c' gids deliminators,\n\
die welk in de naam van een voorwerp niet toegestaan is."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O novo \"%s\" de nome contem deliminators de guia\n\
de '%c' que nao são permitidos num nome do objeto."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Den nye navne \"%s\" inneholder '%c' katalog deliminators\n\
som ikke tillater i et objekts navn."
#else
"The new name \"%s\" contains '%c' directory\n\
deliminators which are not allowed in an object's name"
#endif
            ,
            value, G_DIR_SEPARATOR
          );
          MESSAGE_RENAME_FAILED(s);
          g_free(s);
          return;
      }

      /* Get last selected object */
      glist = fb->selection_end;
      i = (glist != NULL) ? (gint)glist->data : -1;
      o = FileBrowserGetObject(fb, i);
      if((o != NULL) ? (o->full_path != NULL) : FALSE)
      {
          const gchar *cur_location = FileBrowserGetLocation(fb);
          gchar   *old_full_path = STRDUP(o->full_path),
                  *new_full_path = STRDUP(
                      PrefixPaths(cur_location, value)
                  );
          struct stat lstat_buf;

          /* New name and old name the same? */
          if(!strcmp(o->name, value))
          {

          }
          /* Make that the new name does not refer to an object that
           * already exists
           */
#ifdef _WIN32
          else if(stat(new_full_path, &lstat_buf) == 0)
#else
          else if(lstat(new_full_path, &lstat_buf) == 0)
#endif
          {
            gchar *s = g_strdup_printf(
"An object by the name of \"%s\" already exists.",
                value
            );
            MESSAGE_RENAME_FAILED(s);
            g_free(s);
          }
          /* Rename */
          else if(rename(old_full_path, new_full_path))
          {
            const gint error_code = (gint)errno;
            gchar *s = g_strdup_printf(
                "%s.",
                g_strerror(error_code)
            );
            MESSAGE_RENAME_FAILED(s);
            g_free(s);
          }
          else
          {
            /* Rename successful */

            /* Update the list */
            FileBrowserListUpdate(fb, NULL);

            /* Reselect object */
            for(i = 0; i < fb->total_objects; i++)
            {
                o = fb->object[i];
                if((o != NULL) ? (o->full_path == NULL) : TRUE)
                  continue;

                if(!strcmp(o->full_path, new_full_path))
                {
                  /* Select this object */
                  fb->focus_object = i;
                  fb->selection = g_list_append(
                      fb->selection, (gpointer)i
                  );
                  fb->selection_end = g_list_last(fb->selection);

                  /* Scroll to this object */
                  FileBrowserListMoveToObject(fb, i, 0.5f);

                  break;
                }
            }
            FileBrowserListQueueDraw(fb);

            /* Update selected objects on entry */
            FileBrowserEntrySetSelectedObjects(fb);
          }

          g_free(old_full_path);
          g_free(new_full_path);
      }

#undef MESSAGE_RENAME_FAILED
#undef TITLE_RENAME_FAILED
}

/*
 *    Rename callback.
 */
static void FileBrowserRenameCB(GtkWidget *widget, gpointer data)
{
      gint i, x, y, width, height;
      const GList *glist;
      const FileBrowserObject *o;
      FileBrowser *fb = FILE_BROWSER(data);
      if((fb == NULL) || FPromptIsQuery())
          return;

      /* Get last selected object */
      glist = fb->selection_end;
      i = (glist != NULL) ? (gint)glist->data : -1;
      o = FileBrowserGetObject(fb, i);
      if(o == NULL)
          return;

      /* Get fprompt geometry based on list display format */
      x = o->x;
      y = o->y;
      width = MAX(o->width + 4, 100);
      height = -1;
      switch(fb->list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          if(fb->list_vsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_vsb);
            GtkAdjustment *adj = range->adjustment;
            y -= (gint)((adj != NULL) ? adj->value : 0.0f);
          }
          break;
        case FB_LIST_FORMAT_STANDARD:
          if(fb->list_hsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_hsb);
            GtkAdjustment *adj = range->adjustment;
            x -= (gint)((adj != NULL) ? adj->value : 0.0f);
          }
          break;
      }

      /* Get root window relative coordinates of the object for
       * placement of the floating prompt.
       */
      if(fb->list_da != NULL)
      {
          GtkWidget *w = fb->list_da;
          GdkWindow *window = w->window;
          if(window != NULL)
          {
            gint rx, ry;
            gdk_window_get_deskrelative_origin(
                window, &rx, &ry
            );
            x += rx;
            y += ry;
          }
      }
      /* At this point x and y should now be the root window
       * relative position for the floating prompt
       */
      FPromptSetTransientFor(fb->toplevel);
      FPromptSetPosition(x - 2, y - 2);
      FPromptMapQuery(
          NULL,               /* No label */
          o->name,
          NULL,               /* No tooltip */
          FPROMPT_MAP_NO_MOVE,      /* Map code */
          width, height,            /* Width and height */
          0,                        /* No buttons */
          fb,                       /* Data */
          NULL,               /* No browse callback */
          FileBrowserRenameFPromptCB,
          NULL                /* No cancel callback */
      );
}

/*
 *    CHMod fprompt callback.
 */
static void FileBrowserCHModFPromptCB(
      gpointer data, const gchar *value
)
{
      gint i;
      GList *glist;
      FileBrowserObject *o;
      FileBrowser *fb = FILE_BROWSER(data);
      if((fb == NULL) || STRISEMPTY(value))
          return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_CHMOD_FAILED     "Cambie El Modo Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_CHMOD_FAILED     "Changer Le Mode A échoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_CHMOD_FAILED     "Ändern Sie Versagten Modus"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_CHMOD_FAILED     "Cambiare Il Modo Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_CHMOD_FAILED     "Verandeer Modus Verzuimde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_CHMOD_FAILED     "Mude Modo Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_CHMOD_FAILED     "Forandr Sviktet Modus"
#else
# define TITLE_CHMOD_FAILED     "Change Mode Failed"
#endif

#define MESSAGE_CHMOD_FAILED(s)           {     \
 CDialogSetTransientFor(fb->toplevel);          \
 CDialogGetResponse(                      \
  TITLE_CHMOD_FAILED,                     \
  (s), NULL,                              \
  CDIALOG_ICON_WARNING,                   \
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK  \
 );                                 \
 CDialogSetTransientFor(NULL);                  \
}

      /* Get last selected object */
      glist = fb->selection_end;
      i = (glist != NULL) ? (gint)glist->data : -1;
      o = FileBrowserGetObject(fb, i);
      if((o != NULL) ? (o->full_path != NULL) : FALSE)
      {
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
          mode_t m = 0;
          const gchar *s = value;

          /* Begin parsing value to obtain new mode value */
          while(ISBLANK(*s))
            s++;

          /* User */
          /* Read */
          if(*s != '\0')
          {
            if(tolower(*s) == 'r')
                m |= S_IRUSR;
            s++;
          }
          /* Write */
          if(*s != '\0')
          {
            if(tolower(*s) == 'w')
                m |= S_IWUSR;
            s++;
          }
          /* Execute */
          if(*s != '\0')
          {
            if(tolower(*s) == 'x')
                m |= S_IXUSR;
            else if(tolower(*s) == 's')
                m |= S_ISUID;
            s++;
          }
          /* Group */
          /* Read */
          if(*s != '\0')
          {
            if(tolower(*s) == 'r')
                m |= S_IRGRP;
            s++;
          }
          /* Write */
          if(*s != '\0')
          {
            if(tolower(*s) == 'w')
                m |= S_IWGRP;
            s++;
          }
          /* Execute */
          if(*s != '\0')
          {
            if(tolower(*s) == 'x')
                m |= S_IXGRP;
            else if(tolower(*s) == 'g')
                m |= S_ISGID;
            s++;
          }
          /* Other */
          /* Read */
          if(*s != '\0')
          {
            if(tolower(*s) == 'r')
                m |= S_IROTH;
            s++;
          }
          /* Write */
          if(*s != '\0')
          {
            if(tolower(*s) == 'w')
                m |= S_IWOTH;
            s++;
          }
          /* Execute */
          if(*s != '\0')
          {
            if(tolower(*s) == 'x')
                m |= S_IXOTH;
            else if(tolower(*s) == 't')
                m |= S_ISVTX;
            s++;
          }

          /* CHMod */
          if(chmod(o->full_path, m))
          {
            const gint error_code = (gint)errno;
            gchar *s = g_strdup_printf(
                "%s.",
                g_strerror(error_code)
            );
            MESSAGE_CHMOD_FAILED(s);
            g_free(s);
          }
          else
          {
            /* Statistics */
#ifdef _WIN32
            if(stat(o->full_path, &o->lstat_buf))
#else
            if(lstat(o->full_path, &o->lstat_buf))
#endif
                memset(&o->lstat_buf, 0x00, sizeof(struct stat));

            /* Update values */
            FileBrowserObjectUpdateValues(fb, o);
          }
#endif
      }

      /* Need to redraw and update the value in the entry
       * since one of the selected object's names may have
       * changed
       */
      FileBrowserListQueueDraw(fb);
      FileBrowserEntrySetSelectedObjects(fb);

#undef MESSAGE_RENAME_FAILED
#undef TITLE_RENAME_FAILED
}

/*
 *    CHMod callback.
 */
static void FileBrowserCHModCB(GtkWidget *widget, gpointer data)
{
      gint i, x, y, width, height;
      mode_t m;
      gchar *value;
      const GList *glist;
      const FileBrowserObject *o;
      const FileBrowserColumn *column, *column2;
      FileBrowser *fb = FILE_BROWSER(data);
      if((fb == NULL) || FPromptIsQuery())
          return;

      /* Get last selected object */
      glist = fb->selection_end;
      i = (glist != NULL) ? (gint)glist->data : -1;
      o = FileBrowserGetObject(fb, i);
      if(o == NULL)
          return;

      m = o->lstat_buf.st_mode;

#ifdef S_ISLNK
      /* Cannot chmod links */
      if(S_ISLNK(m))
      {
          CDialogSetTransientFor(fb->toplevel);
          CDialogGetResponse(
"Change Mode Failed",
"You cannot change the mode of symbolic link objects",
            NULL,
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }
#endif

#if !defined(S_IRUSR) || !defined(S_IWUSR) || !defined(S_IXUSR)
      /* Since permissions not supported we cannot chmod */
      if(TRUE)
      {
          CDialogSetTransientFor(fb->toplevel);
          CDialogGetResponse(
"Change Mode Failed",
"Changing the mode of objects is not supported on this system.",
            NULL,
            CDIALOG_ICON_WARNING,
            CDIALOG_BTNFLAG_OK,
            CDIALOG_BTNFLAG_OK
          );
          CDialogSetTransientFor(NULL);
          return;
      }
#else
      /* Format current value */
      value = g_strdup_printf(
          "%c%c%c%c%c%c%c%c%c",
          (m & S_IRUSR) ? 'r' : '-',
          (m & S_IWUSR) ? 'w' : '-',
          (m & S_ISUID) ? 'S' :
            ((m & S_IXUSR) ? 'x' : '-'),
          (m & S_IRGRP) ? 'r' : '-',
          (m & S_IWGRP) ? 'w' : '-',
          (m & S_ISGID) ? 'G' :
            ((m & S_IXGRP) ? 'x' : '-'),
          (m & S_IROTH) ? 'r' : '-',
          (m & S_IWOTH) ? 'w' : '-',
          (m & S_ISVTX) ? 'T' :
            ((m & S_IXOTH) ? 'x' : '-')
      );
#endif

      /* Get fprompt geometry based on list display format */
      x = o->x;
      y = o->y;
      width = MAX(o->width + 4, 80);
      height = -1;
      switch(fb->list_format)
      {
        case FB_LIST_FORMAT_VERTICAL_DETAILS:
        case FB_LIST_FORMAT_VERTICAL:
          if(fb->list_vsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_vsb);
            GtkAdjustment *adj = range->adjustment;
            y -= (gint)((adj != NULL) ? adj->value : 0.0f);
          }
          column = FileBrowserListGetColumn(fb, 1);
          if(column != NULL)
            x += column->position;
          column2 = FileBrowserListGetColumn(fb, 2);
          if((column != NULL) && (column2 != NULL))
            width = MAX(column2->position - column->position + 4, 80);
          break;

        case FB_LIST_FORMAT_STANDARD:
          if(fb->list_hsb != NULL)
          {
            GtkRange *range = GTK_RANGE(fb->list_hsb);
            GtkAdjustment *adj = range->adjustment;
            x -= (gint)((adj != NULL) ? adj->value : 0.0f);
          }
          break;
      }
      /* Get root window relative coordinates of the object for
       * placement of the floating prompt
       */
      if(fb->list_da != NULL)
      {
          GtkWidget *w = fb->list_da;
          GdkWindow *window = w->window;
          if(window != NULL)
          {
            gint rx, ry;
            gdk_window_get_deskrelative_origin(
                window, &rx, &ry
            );
            x += rx;
            y += ry;
          }
      }
      /* At this point x and y should now be the root window
       * relative position for the floating prompt
       */
      FPromptSetTransientFor(fb->toplevel);
      FPromptSetPosition(x - 2, y - 2);
      FPromptMapQuery(
          NULL,               /* No label */
          value,
          NULL,               /* No tooltip */
          FPROMPT_MAP_NO_MOVE,      /* Map code */
          width, height,            /* Width and height */
          0,                        /* No buttons */
          fb,                       /* Data */
          NULL,               /* No browse callback */
          FileBrowserCHModFPromptCB,
          NULL                        /* No cancel callback */
      );

      g_free(value);
}

/*
 *    List object delete callback.
 */
static void FileBrowserDeleteCB(GtkWidget *widget, gpointer data)
{
      gint objects_deleted = 0;
      gint i, response;
      gchar *msg;
      GList *glist;
      FileBrowserObject *o;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_DELETE_FAILED  "Borre Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_DELETE_FAILED  "Effacer Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_DELETE_FAILED  "Löschen Sie Versagt"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_DELETE_FAILED  "Cancellare Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_DELETE_FAILED  "Schrap Geverzuimenene"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_DELETE_FAILED  "Anule Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_DELETE_FAILED  "Stryk Failed"
#else
# define TITLE_DELETE_FAILED  "Delete Failed"
#endif

#define MESSAGE_DELETE_FAILED(s)    {     \
 CDialogSetTransientFor(fb->toplevel);          \
 CDialogGetResponse(                      \
  TITLE_DELETE_FAILED,                    \
  (s), NULL,                              \
  CDIALOG_ICON_WARNING,                   \
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK  \
 );                                 \
 CDialogSetTransientFor(NULL);                  \
}

      if(CDialogIsQuery())
          return;

      /* Get selection */
      glist = fb->selection;
      if(glist == NULL)
          return;

      FileBrowserSetBusy(fb, TRUE);

      /* Format confirmation message, if only one object is selected
       * then confirm with its name. Otherwise confirm the number of
       * objects to be deleted.
       */
      i = g_list_length(glist);
      if(i == 1)
      {
          i = (gint)glist->data;
          o = FileBrowserGetObject(fb, i);
          if((o != NULL) ? (o->name != NULL) : FALSE)
            msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"¿Usted está seguro que usted quiere borrar \"%s\"?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Etes-vous sûr que vous voulez effacer \"%s\"?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Sind sie sicher sie \"%s\" wollen löschen?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Lei sono sicuro che lei vuole cancellare \"%s\"?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bent u zeker u \"%s\" wil schrappen?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Estão seguro quer anular \"%s\"?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Er De sikker De stryker \"%s\"?"
#else
"Are you sure you want to delete \"%s\"?"
#endif
                , o->name
            );
          else
            msg = STRDUP(
"Are you sure you want to delete this (unnamed) object?"
            );
      }
      else
      {
          msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"¿Borra %i objetos escogidos?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Efface %i objets choisis?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen Sie %i ausgewählte Objekte?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancella %i oggetti scelti?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap %i geselecteerde voorwerpen?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule %i objetos selecionados?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk %i valgte ut objekt?"
#else
"Delete %i selected objects?"
#endif
            , i
          );
      }

      /* Confirm delete */
      CDialogSetTransientFor(fb->toplevel);
      response = CDialogGetResponseIconData(
#if defined(PROG_LANGUAGE_SPANISH)
"Confirme Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Confirmer Effacer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Bestätigen Sie Löscht"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Confermare Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bevestiig Schrappet"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Confirme Anula"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Bekreft Delete"
#else
"Confirm Delete"
#endif
          , msg, NULL,
          (guint8 **)icon_trash_32x32_xpm,
          CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
          CDIALOG_BTNFLAG_NO
      );
      CDialogSetTransientFor(NULL);
      g_free(msg);
      if(response != CDIALOG_RESPONSE_YES)
      {
          FileBrowserSetBusy(fb, FALSE);
          return;
      }

      /* Iterate through selected objects */
      while(glist != NULL)
      {
          i = (gint)glist->data;
          o = FileBrowserGetObject(fb, i);
          if((o != NULL) ? (o->full_path != NULL) : FALSE)
          {
            const gchar *full_path = o->full_path;
            if(ISLPATHDIR(full_path))
            {
                /* Delete directory */
                if(rmdir(full_path))
                {
                  const gint error_code = (gint)errno;
                  gchar *s = g_strdup_printf(
                      "%s.",
                      g_strerror(error_code)
                  );
                  MESSAGE_DELETE_FAILED(s);
                  g_free(s);
                }
                else
                {
                  objects_deleted++;
                }
            }
            else
            {
                /* Delete object */
                if(unlink((const char *)full_path))
                {
                  const gint error_code = (gint)errno;
                  gchar *s = g_strdup_printf(
                      "%s.",
                      g_strerror(error_code)
                  );
                  MESSAGE_DELETE_FAILED(s);
                  g_free(s);
                }
                else
                {
                  objects_deleted++;
                }
            }
          }

          glist = g_list_next(glist);
      }

      /* Need to refresh everything due to delete */
      FileBrowserRefreshCB(fb->refresh_btn, fb);

      FileBrowserSetBusy(fb, FALSE);

#undef MESSAGE_DELETE_FAILED
#undef TITLE_DELETE_FAILED
}


/*
 *    Entry complete path "key_press_event" or "key_release_event"
 *    signal callback.
 */
static gint FileBrowserEntryCompletePathKeyCB(
      GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
      gint status = FALSE;
      gboolean press;
      GtkEntry *entry = GTK_ENTRY(widget);
      FileBrowser *fb = FILE_BROWSER(data);
      if((entry == NULL) || (key == NULL) || (fb == NULL))
          return(status);

      press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;

#define SIGNAL_EMIT_STOP      {           \
 gtk_signal_emit_stop_by_name(                  \
  GTK_OBJECT(widget),                     \
  press ?                           \
   "key_press_event" : "key_release_event"      \
 );                                 \
}

      switch(key->keyval)
      {
        case GDK_Tab:
          /* Skip this if the shift or ctrl keys are held */
          if((key->state & GDK_CONTROL_MASK) ||
             (key->state & GDK_SHIFT_MASK)
          )
            return(status);

          if(press)
          {
            gchar *s = STRDUP(gtk_entry_get_text(entry));
            if(s != NULL)
            {
                gint status;

                /* Because the entry is usually blank or not an
                 * absolute path, the path must first be made into
                 * an absolute path before completing it
                 */
                if(!ISPATHABSOLUTE(s))
                {
                  if(STRISEMPTY(s))
                  {
                      g_free(s);
                      s = STRDUP(FileBrowserGetLocation(fb));
                  }
                  else
                  {
                      gchar *s2 = STRDUP(PrefixPaths(
                        FileBrowserGetLocation(fb), s
                      ));
                      g_free(s);
                      s = s2;
                  }
                }

                s = CompletePath(s, &status);
                gtk_entry_set_text(entry, (s != NULL) ? s : "");
                gtk_entry_set_position(entry, -1);
                g_free(s);

                switch(status)
                {
                  case COMPLETE_PATH_NONE:
                  case COMPLETE_PATH_AMBIGUOUS:
                  gdk_beep();
                  break;
                }
            }
          }
          SIGNAL_EMIT_STOP
          status = TRUE;
          break;
      }

#undef SIGNAL_EMIT_STOP

      return(status);
}

/*
 *    Entry enter callback.
 *
 *    This function is slightly different from the OK callback
 *    since it will switch to a directory if the single value
 *    is a directory.
 *
 *    This function will call the OK callback if there are
 *    multiple values specified (when it sees a ',' deliminator)
 *    or the single object is specified and (if it actually exists)
 *    does not lead to a directory.
 */
static void FileBrowserEntryEnterCB(GtkWidget *widget, gpointer data)
{
      gchar *s;
      GtkWidget *w;
      FileBrowser *fb = FILE_BROWSER(data);
      if(fb == NULL)
          return;

      w = fb->entry;
      if(w == NULL)
          return;

      /* Get file name entry value */
      s = gtk_entry_get_text(GTK_ENTRY(w));
      if((s != NULL) ? (*s == '\0') : TRUE)
          return;

      s = STRDUP(s);    /* Make a copy of s */

      /* Filter specified? */
      if((strchr(s, '*') != NULL) || (strchr(s, '?') != NULL))
      {
          /* Reget the listing with the newly specified filter */
          FileBrowserListUpdate(fb, s);
      }
      /* Multiple objects specified? */
      else if(strchr(s, ','))
      {
          /* Multiple objects selected, just call the OK callback */
          FileBrowserOKCB(fb->ok_btn, fb);
      }
      /* Current directory specified? */
      else if(!strcmp(s, "."))
      {
          /* Do nothing */
      }
      /* Parent directory specified? */
      else if(!strcmp(s, ".."))
      {
          /* Go to parent location */
          const gchar *cur_location = FileBrowserGetLocation(fb);
          gchar *parent = (cur_location != NULL) ?
            g_dirname(cur_location) : NULL;
          FileBrowserSetLocation(fb, parent);
          g_free(parent);
      }
      /* Home directory specified? */
      else if(*s == '~')
      {
          /* Go to home directory */
          FileBrowserSetLocation(fb, s);
      }
      else
      {
          /* All else assume single object specified, it may or may
           * not actually exist
           */
          gchar *full_path;
          struct stat stat_buf;

          /* Check if the object specified is specified as a full
           * path and if it is not then prefix the current location
           * to it
           */
          if(g_path_is_absolute(s))
          {
            full_path = STRDUP(s);
          }
          else
          {
            const gchar *cur_location = FileBrowserGetLocation(fb);
            full_path = STRDUP(PrefixPaths(cur_location, s));
          }

          /* Check if the object specified by full_path exists and if
           * it leads to a directory
           */
          if(stat(full_path, &stat_buf))
          {
            /* Cannot stat object (note that it may exist locally
             * as a symbolic link), the user may want to save as a
             * new object, so go ahead and call the OK callback on
             * this
             */
            FileBrowserOKCB(fb->ok_btn, fb);
          }
          else
          {
            /* If the object specified by full_path is a directory
             * then go to that directory, otherwise call the OK
             * callback
             */
#ifdef S_ISDIR
            if(S_ISDIR(stat_buf.st_mode))
#else
            if(FALSE)
#endif
                FileBrowserSetLocation(fb, full_path);
            else
                FileBrowserOKCB(fb->ok_btn, fb);
          }

          g_free(full_path);
      }

      g_free(s);
}

/*
 *    Types popup list box "changed" signal callback.
 */
static void FileBrowserTypesPUListChangedCB(
      pulistbox_struct *pulistbox, gint i, gpointer data
)
{
      pulist_struct *pulist;
      fb_type_struct *t_sel, *t;
      FileBrowser *fb = FILE_BROWSER(data);
      if((pulistbox == NULL) || (fb == NULL))
          return;

      pulist = PUListBoxGetPUList(pulistbox);
      if(pulist == NULL)
          return;

      t_sel = FB_TYPE(PUListGetItemData(pulist, i));
      if(t_sel == NULL)
          return;

      t = &fb->cur_type;

      g_free(t->ext);
      t->ext = STRDUP(t_sel->ext);

      g_free(t->name);
      t->name = STRDUP(t_sel->name);

      /* Update the listings only if not frozen */
      if(fb->freeze_count <= 0)
      {
          fb->freeze_count++;

          /* Update the list */
          FileBrowserListUpdate(fb, NULL);

          fb->freeze_count--;
      }
}


/*
 *    Initializes the file browser.
 */
gint FileBrowserInit(void)
{
      const gint  border_major = 5,
                  border_minor = 2;
      gint i;
      GdkWindow *window;
      GtkAdjustment *adj;
      GtkAccelGroup *accelgrp;
      GtkWidget *w, *parent, *parent2, *parent3, *parent4;
      pulist_struct *pulist;
      pulistbox_struct *pulistbox;
      FileBrowser *fb = &file_browser;

      /* Reset values */
      fb->busy_count = 0;
      fb->freeze_count = 0;
      fb->accelgrp = accelgrp = gtk_accel_group_new();
      fb->cur_busy = gdk_cursor_new(GDK_WATCH);
      fb->cur_column_hresize = gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);
      fb->cur_translate = gdk_cursor_new(GDK_FLEUR);
      fb->list_pm = NULL;
      fb->vsb_map_state = FALSE;
      fb->hsb_map_state = FALSE;
      fb->cur_location = NULL;
      fb->block_loop_level = 0;
      fb->user_response = FALSE;
      fb->list_format = FB_LIST_FORMAT_STANDARD;
      fb->column = NULL;
      fb->total_columns = 0;
      memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));
      fb->selected_path = NULL;
      fb->total_selected_paths = 0;
      fb->icon = NULL;
      fb->total_icons = 0;
#ifdef _WIN32
      fb->uid = 0;
      fb->euid = 0;
      fb->gid = 0;
      fb->egid = 0;
#else
      fb->uid = getuid();
      fb->euid = geteuid();
      fb->gid = getgid();
      fb->egid = getegid();
#endif
#ifdef _WIN32
      fb->home_path = STRDUP("C:\\");
#else
      fb->home_path = STRDUP(g_getenv("HOME"));
#endif
      fb->drive_path = NULL;
      fb->total_drive_paths = 0;
      fb->focus_object = -1;
      fb->selection = fb->selection_end = NULL;
      fb->object = NULL;
      fb->total_objects = 0;
      fb->objects_per_row = 0;
      fb->last_single_select_object = -1;
      fb->show_hidden_objects = TRUE;
      fb->button = 0;
      fb->drag_last_x = 0;
      fb->drag_last_y = 0;
      fb->last_button1_release_time = 0l;

      /* Toplevel */
      fb->toplevel = w = gtk_window_new(GTK_WINDOW_DIALOG);
      gtk_widget_set_usize(w, FB_WIDTH, FB_HEIGHT);
      gtk_window_set_title(GTK_WINDOW(w), "Select File");
#ifdef PROG_NAME
      gtk_window_set_wmclass(
          GTK_WINDOW(w), "fileselection", PROG_NAME
      );
#endif
      gtk_widget_realize(w);
      window = w->window;
      if(window != NULL)
      {
          GdkGeometry geo;

          gdk_window_set_decorations(
            window,
            GDK_DECOR_BORDER | GDK_DECOR_TITLE | GDK_DECOR_MENU |
            GDK_DECOR_MINIMIZE
          );
          gdk_window_set_functions(
            window,
            GDK_FUNC_MOVE | GDK_FUNC_MINIMIZE | GDK_FUNC_CLOSE
          );

          geo.min_width = 100;
          geo.min_height = 70;
          geo.max_width = gdk_screen_width() - 10;
          geo.max_height = gdk_screen_height() - 10;
          geo.base_width = 0;
          geo.base_height = 0;
          geo.width_inc = 1;
          geo.height_inc = 1;
          geo.min_aspect = 1.3f;
          geo.max_aspect = 1.3f;
          gdk_window_set_geometry_hints(
            window, &geo,
            GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE |
            GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC
          );
      }
      gtk_widget_add_events(
          w,
          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "delete_event",
          GTK_SIGNAL_FUNC(FileBrowserDeleteEventCB), fb
      );
      gtk_container_border_width(GTK_CONTAINER(w), 0);
      gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
      parent = w;

      /* Main vbox */
      fb->main_vbox = w = gtk_vbox_new(FALSE, border_major);
      gtk_container_border_width(GTK_CONTAINER(w), border_major);
      gtk_container_add(GTK_CONTAINER(parent), w);
      gtk_widget_show(w);
      parent = w;


      w = gtk_hbox_new(FALSE, border_major);
      gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
      gtk_widget_show(w);
      parent2 = w;


      /* GtkHBox for the location label and locations popup list */
      w = gtk_hbox_new(FALSE, border_minor);
      gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
      gtk_widget_show(w);
      parent3 = w;

      w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Ubicación:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Emplacement:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ort:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Posizione:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Plaats:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Localidade:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Plassering:"
#else
"Location:"
#endif
      );
        gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_widget_show(w);

      /* Locations popup list box */
      fb->loc_pulistbox = pulistbox = PUListBoxNew(
          parent3,
          -1, 20 + (2 * 3)
      );
      PUListBoxSetChangedCB(
          pulistbox,
          FileBrowserLocPUListChangedCB, fb
      );
      PUListBoxMap(pulistbox);


      /* Go To Parent */
      fb->goto_parent_btn = w = GUIButtonPixmap(
          (guint8 **)icon_folder_parent_20x20_xpm
      );
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserGoToParentCB), fb
      );
#if 0
      gtk_accel_group_add(
          accelgrp, GDK_BackSpace, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
#endif
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"El Padre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Le Parent"
#elif defined(PROG_LANGUAGE_GERMAN)
"Elternteil"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Genitore"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ouder"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Pai"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Mor Eller Far"
#else
"Parent"
#endif
      );
      gtk_widget_show(w);

      /* New Directory */
      fb->new_directory_btn = w = GUIButtonPixmap(
          (guint8 **)icon_folder_closed_20x20_xpm
      );
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserNewDirectoryCB), fb
      );
#if 0
      gtk_accel_group_add(
          accelgrp, GDK_Insert, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
#endif
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"La Guía Nueva"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nouvel Annuaire"
#elif defined(PROG_LANGUAGE_GERMAN)
"Neues Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Elenco Nuovo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Nieuwe Gids"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Novo Guia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ny Directory"
#else
"New Directory"
#endif
      );
      gtk_widget_show(w);

      /* Rename */
      fb->rename_btn = w = GUIButtonPixmap(
          (guint8 **)icon_rename_20x20_xpm
      );
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserRenameCB), fb
      );
#if 0
      gtk_accel_group_add(
          accelgrp, GDK_F2, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
#endif
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Reagrupe"
#elif defined(PROG_LANGUAGE_FRENCH)
"Renommer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Benennen Sie Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinominare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Herdoop"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Nome"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ombenevn"
#else
"Rename"
#endif
      );
      gtk_widget_show(w);

      /* Refresh */
      fb->refresh_btn = w = GUIButtonPixmap(
          (guint8 **)icon_reload_20x20_xpm
      );
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserRefreshCB), fb
      );
#if 0
      gtk_accel_group_add(
          accelgrp, GDK_F5, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
#endif
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Refresque"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rafraîchir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erfrischen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinfrescare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verfris"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refresque-se"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forfrisk"
#else
"Refresh"
#endif
      );
      gtk_widget_show(w);

      /* Show hidden objects */
      fb->show_hidden_objects_tb = w = GUIToggleButtonPixmap(
          (guint8 **)icon_file_hidden_20x20_xpm
      );
      gtk_toggle_button_set_active(
          GTK_TOGGLE_BUTTON(w), fb->show_hidden_objects
      );
      gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "toggled",
          GTK_SIGNAL_FUNC(FileBrowserShowHiddenObjectsToggleCB), fb
      );
      GUISetWidgetTip(w,
"Show Hidden Objects"
      );
      gtk_widget_show(w);

      /* Hbox for list format buttons */
      w = gtk_hbox_new(FALSE, 0);
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_widget_show(w);
      parent3 = w;

      fb->list_format_standard_tb = w = GUIToggleButtonPixmap(
          (guint8 **)icon_fb_list_standard_20x20_xpm
      );
      gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "toggled",
          GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
      );
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar De Estándar"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer De Norme"
#elif defined(PROG_LANGUAGE_GERMAN)
"Standard Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Di Norma"
#elif defined(PROG_LANGUAGE_DUTCH)
"Standaard Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Padrão"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Normal Listing"
#else
"Standard Listing"
#endif
      );
      gtk_widget_show(w);

      fb->list_format_vertical_tb = w = GUIToggleButtonPixmap(
          (guint8 **)icon_fb_list_vertical_20x20_xpm
      );
      gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "toggled",
          GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
      );
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar Detallado"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer Détaillé"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ausführliches Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Dettagliato"
#elif defined(PROG_LANGUAGE_DUTCH)
"Gedetailleerd Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Detailed"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Detaljert Listing"
#else
"Detailed Listing"
#endif
      );
/* Don't offer the use of the vertical list anymore */
/*    gtk_widget_show(w); */

      fb->list_format_vertical_details_tb = w = GUIToggleButtonPixmap(
          (guint8 **)icon_fb_list_vertical_details_20x20_xpm
      );
      gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "toggled",
          GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
      );
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar Detallado"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer Détaillé"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ausführliches Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Dettagliato"
#elif defined(PROG_LANGUAGE_DUTCH)
"Gedetailleerd Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Detailed"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Detaljert Listing"
#else
"Detailed Listing"
#endif
      );
      gtk_widget_show(w);



      /* Table for list */
      w = gtk_table_new(2, 2, FALSE);
      gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
      gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
      gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
      gtk_widget_show(w);
      parent2 = w;

      /* Frame and vbox with in for parenting the list header and
       * the list.
       */
      w = gtk_frame_new(NULL);
      gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
      gtk_table_attach(
          GTK_TABLE(parent2), w,
          0, 1, 0, 1,
          GTK_FILL | GTK_SHRINK | GTK_EXPAND,
          GTK_FILL | GTK_SHRINK | GTK_EXPAND,
          0, 0
      );
      gtk_widget_show(w);
      parent3 = w;
      w = gtk_vbox_new(FALSE, 0);
      gtk_container_add(GTK_CONTAINER(parent3), w);
      gtk_widget_show(w);
      parent3 = w;


      /* List header */
      fb->list_header_da = w = gtk_drawing_area_new();
      gtk_widget_set_usize(w, FB_LIST_HEADER_WIDTH, FB_LIST_HEADER_HEIGHT);
      GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS);
      gtk_widget_add_events(
          w,
          GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
          GDK_FOCUS_CHANGE_MASK |
          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
          GDK_POINTER_MOTION_MASK |
          GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "configure_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "expose_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "focus_in_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "focus_out_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "button_press_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "button_release_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "motion_notify_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "enter_notify_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "leave_notify_event",
          GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
      );
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_widget_show(w);

      /* List */
      fb->list_da = w = gtk_drawing_area_new();
      gtk_widget_set_usize(w, FB_LIST_WIDTH, FB_LIST_HEIGHT);
      GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
      gtk_widget_add_events(
          w,
          GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
          GDK_FOCUS_CHANGE_MASK |
          GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
          GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "configure_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "expose_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "focus_in_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "focus_out_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "key_press_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "key_release_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "button_press_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "button_release_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "motion_notify_event",
          GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
      );
      gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
      gtk_widget_show(w);
      if(w != NULL)
      {
          const GtkTargetEntry dnd_tar_types[] = {
            {"text/plain",          0,    0},
            {"text/uri-list", 0,    1},
            {"STRING",        0,    2}
          };
          const GtkTargetEntry dnd_src_types[] = {
            {"text/plain",          0,    0},
            {"text/uri-list", 0,    1},
            {"STRING",        0,    2}
          };
          GUIDNDSetTar(
            w,
            dnd_tar_types,
            sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
            GDK_ACTION_COPY,        /* Actions */
            GDK_ACTION_COPY,        /* Default action if same */
            GDK_ACTION_COPY,        /* Default action */
            FileBrowserDragDataReceivedCB,
            fb
          );
          gtk_signal_connect_after(
            GTK_OBJECT(w), "drag_motion",
            GTK_SIGNAL_FUNC(FileBrowserDragMotionCB), fb
          );
          GUIDNDSetSrc(
            w,
            dnd_src_types,
            sizeof(dnd_src_types) / sizeof(GtkTargetEntry),
            GDK_ACTION_COPY | GDK_ACTION_MOVE |
                GDK_ACTION_LINK,          /* Actions */
            GDK_BUTTON1_MASK,       /* Buttons */
            NULL,
            FileBrowserDragDataGetCB,
            FileBrowserDragDataDeleteCB,
            NULL,
            fb
          );
      }

      /* List vertical scrollbar */
      adj = (GtkAdjustment *)gtk_adjustment_new(
          0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
      );
      fb->list_vsb = w = gtk_vscrollbar_new(adj);
      gtk_table_attach(
          GTK_TABLE(parent2), w,
          1, 2, 0, 1,
          0,
          GTK_FILL | GTK_SHRINK | GTK_EXPAND,
          0, 0
      );
      gtk_signal_connect(
          GTK_OBJECT(adj), "value_changed",
          GTK_SIGNAL_FUNC(FileBrowserListScrollCB), fb
      );

      /* List's horizontal GtkScrollBar */
      adj = (GtkAdjustment *)gtk_adjustment_new(
          0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
      );
      fb->list_hsb = w = gtk_hscrollbar_new(adj);
      gtk_table_attach(
          GTK_TABLE(parent2), w,
          0, 1, 1, 2,
          GTK_FILL | GTK_SHRINK | GTK_EXPAND,
          0,
          0, 0
      );
      gtk_signal_connect(
          GTK_OBJECT(adj), "value_changed",
          GTK_SIGNAL_FUNC(FileBrowserListScrollCB), fb
      );

      /* List right-click menu */
      if(TRUE)
      {
          GtkWidget *menu = (GtkWidget *)GUIMenuCreate();
          guint8 **icon;
          const gchar *label;
          guint accel_key, accel_mods;
          gpointer mclient_data = fb;
          void (*func_cb)(GtkWidget *w, gpointer);

#define DO_ADD_MENU_ITEM_LABEL      {           \
 w = GUIMenuItemCreate(                   \
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,     \
  icon, label, accel_key, accel_mods, NULL,     \
  mclient_data, func_cb                   \
 );                                 \
}
#define DO_ADD_MENU_ITEM_CHECK      {           \
 w = GUIMenuItemCreate(                   \
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,     \
  icon, label, accel_key, accel_mods, NULL,     \
  mclient_data, func_cb                   \
 );                                 \
}
#define DO_ADD_MENU_SEP {                 \
 w = GUIMenuItemCreate(                   \
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,     \
  NULL, NULL, 0, 0, NULL,                 \
  NULL, NULL                              \
 );                                 \
}

          icon = (guint8 **)icon_select_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Selecto"
#elif defined(PROG_LANGUAGE_FRENCH)
"Choisir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erlesen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scegliere"
#elif defined(PROG_LANGUAGE_DUTCH)
"Uitgezocht"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Selecione"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Utvalgt"
#else
"Select"
#endif
          ;
          accel_key = GDK_Return;
          accel_mods = 0;
          func_cb = FileBrowserEntryEnterCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->select_mi = w; */

          DO_ADD_MENU_SEP

          icon = (guint8 **)icon_folder_parent_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Padre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Le Parent"
#elif defined(PROG_LANGUAGE_GERMAN)
"Elternteil"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Genitore"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ouder"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Pai"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Mor Eller Far"
#else
"Parent"
#endif
          ;
          accel_key = GDK_BackSpace;
          accel_mods = 0;
          func_cb = FileBrowserGoToParentCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->goto_parent_mi = w; */

          icon = (guint8 **)icon_reload_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Refresque"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rafraîchir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erfrischen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinfrescare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verfris"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refresque-se"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forfrisk"
#else
"Refresh"
#endif
          ;
          accel_key = GDK_F5;
          accel_mods = 0;
          func_cb = FileBrowserRefreshCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->refresh_mi = w; */

          DO_ADD_MENU_SEP

          icon = (guint8 **)icon_folder_closed_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Guía Nueva"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nouvel Annuaire"
#elif defined(PROG_LANGUAGE_GERMAN)
"Neues Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Elenco Nuovo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Nieuwe Gids"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Novo Guia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ny Directory"
#else
"New Directory"
#endif
          ;
          accel_key = GDK_Insert;
          accel_mods = 0;
          func_cb = FileBrowserNewDirectoryCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->new_directory_mi = w; */

          icon = (guint8 **)icon_rename_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Reagrupe"
#elif defined(PROG_LANGUAGE_FRENCH)
"Renommer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Benennen Sie Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinominare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Herdoop"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Nome"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ombenevn"
#else
"Rename"
#endif
          ;
          accel_key = GDK_F2;
          accel_mods = 0;
          func_cb = FileBrowserRenameCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->rename_mi = w; */

          icon = (guint8 **)icon_chmod_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Cambie Los Permisos"
#elif defined(PROG_LANGUAGE_FRENCH)
"Changer Des Permissions"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ändern Sie Erlaubnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cambiare I Permessi"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verandeer Toestemmingen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Permissões"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forandr Permissions"
#else
"Change Permissions"
#endif
          ;
          accel_key = GDK_F9;
          accel_mods = 0;
          func_cb = FileBrowserCHModCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->chmod_mi = w; */

          icon = (guint8 **)icon_cancel_20x20_xpm;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk"
#else
"Delete"
#endif
          ;
          accel_key = GDK_Delete;
          accel_mods = 0;
          func_cb = FileBrowserDeleteCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->delete_mi = w; */

          DO_ADD_MENU_SEP

          icon = NULL;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Escoja Todo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Choisir Tout"
#elif defined(PROG_LANGUAGE_GERMAN)
"Wählen Sie Alle Aus"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scegliere Tutto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Selecteer Alle"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Selecione Todo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Utvalgt All"
#else
"Select All"
#endif
          ;
          accel_key = 'a';
          accel_mods = GDK_CONTROL_MASK;
          func_cb = FileBrowserSelectAllCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->select_all_mi = w; */

          icon = NULL;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Rechace Todo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Déssélectionner Tout"
#elif defined(PROG_LANGUAGE_GERMAN)
"Entmarkieren Sie Alle"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Deselezionare Tutto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Heldere Keuze"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Deselect Todo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Klar Selection"
#else
"Unselect All"
#endif
          ;
          accel_key = 'u';
          accel_mods = GDK_CONTROL_MASK;
          func_cb = FileBrowserUnselectAllCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->unselect_all_mi = w; */

          icon = NULL;
          label =
#if defined(PROG_LANGUAGE_SPANISH)
"Invierta La Selección"
#elif defined(PROG_LANGUAGE_FRENCH)
"Inverser La Sélection"
#elif defined(PROG_LANGUAGE_GERMAN)
"Invertieren Sie Auswahl"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Invertire La Selezione"
#elif defined(PROG_LANGUAGE_DUTCH)
"Keer Keuze Om"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Inverta Seleção"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Inverter Selection"
#else
"Invert Selection"
#endif
          ;
          accel_key = 'i';
          accel_mods = GDK_CONTROL_MASK;
          func_cb = FileBrowserInvertSelectionCB;
          DO_ADD_MENU_ITEM_LABEL
/*        fb->invert_selection_mi = w; */



          fb->list_menu = menu;

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP
      }


      w = gtk_hseparator_new();
      gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
      gtk_widget_show(w);


      w = gtk_hbox_new(FALSE, border_major);
      gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
      gtk_widget_show(w);
      parent2 = w;

      /* GtkVBox for the name and type */
      w = gtk_vbox_new(FALSE, border_major);
      gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
      gtk_widget_show(w);
      parent3 = w;

      /* GtkHBox for the name */
      w = gtk_hbox_new(FALSE, border_minor);
        gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
        gtk_widget_show(w);
        parent4 = w;

      w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Archive Nombre:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nom De Fichier:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Dateiname:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Schedare Nome:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Archiveer Naam:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquive Nome:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Arkiver Name:"
#else
"File Name:"
#endif
      );
      gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
      gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
        gtk_widget_show(w);

      fb->entry = w = gtk_entry_new();
      gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "key_press_event",
          GTK_SIGNAL_FUNC(FileBrowserEntryCompletePathKeyCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "key_release_event",
          GTK_SIGNAL_FUNC(FileBrowserEntryCompletePathKeyCB), fb
      );
      gtk_signal_connect(
          GTK_OBJECT(w), "activate",
          GTK_SIGNAL_FUNC(FileBrowserEntryEnterCB), fb
      );
      gtk_widget_show(w);
      GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Entre el nombre del objeto, usted puede especificar más\
 que un objeto (separa cada nombre con un ',' el carácter)"
#elif defined(PROG_LANGUAGE_FRENCH)
"Entrer le nom de l'objet, vous pouvez spécifier plus\
 qu'un objet (sépare chaque nom avec un ',' le caractère)"
#elif defined(PROG_LANGUAGE_GERMAN)
"Tragen Sie den Namen des Objekts, Sie können angeben\
 mehr als ein Objekt ein (trennen Sie jeden Namen mit einem ',' charakter)"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Entrare il nome dell'oggetto, lei può specificare più\
 di un oggetto (separa ogni nome con un ',' il carattere)"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ga de naam van het voorwerp, u zou kunnen specificeren\
 meer than een voorwerp binnen (scheid elk naam met een ',' teken)"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Entre o nome do objeto, você pode especificar mais de\
 um objeto (separa cada nome com um ',' caráter)"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Gå inn i navnet av objektet, De spesifiserer mere enn\
 et objekt (separere hver navn med et ',' karakter)"
#else
"Enter the name of the object, you may specify more than\
 one object (separate each name with a ',' character)"
#endif
      );
      if(w != NULL)
      {
          const GtkTargetEntry dnd_tar_types[] = {
            {"text/plain",          0,    0},
            {"text/uri-list", 0,    1},
            {"STRING",        0,    2}
          };
          GUIDNDSetTar(
            w,
            dnd_tar_types,
            sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
            GDK_ACTION_COPY,        /* Actions */
            GDK_ACTION_COPY,        /* Default action if same */
            GDK_ACTION_COPY,        /* Default action */
            FileBrowserDragDataReceivedCB,
            fb
          );
          gtk_signal_connect_after(
            GTK_OBJECT(w), "drag_motion",
            GTK_SIGNAL_FUNC(FileBrowserDragMotionCB), fb
          );
      }

      /* GtkHBox for the type */
      w = gtk_hbox_new(FALSE, border_minor);    
      gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
      gtk_widget_show(w);
      parent4 = w;

      w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Tipo Archivo:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Type De Fichier:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Legt Typ Ab:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Tipo Di File:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Archiveer Type:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquivo Tipo:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Typen Arkivet:"
#else
"File Type:"
#endif
      );
      gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
      gtk_widget_show(w);

      /* Types popup list box */
      fb->types_pulistbox = pulistbox = PUListBoxNew(
          parent4,
          -1, -1
      );
      PUListBoxSetChangedCB(
          pulistbox,
          FileBrowserTypesPUListChangedCB, fb
      );
      PUListBoxMap(pulistbox);
      pulist = PUListBoxGetPUList(pulistbox);
      fb->freeze_count++;
      i = PUListAddItem(pulist, FB_DEFAULT_TYPE_STR);
      if(i > -1)
      {
          fb_type_struct *t = FB_TYPE(g_malloc0(sizeof(fb_type_struct)));
          if(t != NULL)
          {
            t->ext = STRDUP("*.*");
            t->name = STRDUP("All Files");
            PUListSetItemDataFull(
                pulist, i,
                t, FileBrowserTypesPUListItemDestroyCB
            );
          }
          PUListBoxSetLinesVisible(pulistbox, 1);
          PUListBoxSelect(pulistbox, i);
          FileBrowserTypesPUListChangedCB(pulistbox, i, fb);
      }
      fb->freeze_count--;

      /* Vbox for ok and cancel buttons */
      w = gtk_vbox_new(TRUE, border_minor);
      gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
      gtk_widget_show(w);
      parent3 = w;


      /* OK button */
      fb->ok_btn = w = GUIButtonPixmapLabelH(
          (guint8 **)icon_ok_20x20_xpm, "OK", &fb->ok_btn_label
      );
      GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
      gtk_widget_set_usize(
          w,
          GUI_BUTTON_HLABEL_WIDTH_DEF,
          GUI_BUTTON_HLABEL_HEIGHT_DEF
      );
      gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserOKCB), fb
      );
#if 0
      gtk_accel_group_add(
          accelgrp, GDK_Return, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
      gtk_accel_group_add(
          accelgrp, GDK_3270_Enter, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
      gtk_accel_group_add(
          accelgrp, GDK_KP_Enter, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
      gtk_accel_group_add(
          accelgrp, GDK_ISO_Enter, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
#endif
      gtk_widget_show(w);

      /* Cancel button */
      fb->cancel_btn = w = GUIButtonPixmapLabelH(
          (guint8 **)icon_cancel_20x20_xpm, "Cancel", &fb->cancel_btn_label
      );
      GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
      gtk_widget_set_usize(
          w,
          GUI_BUTTON_HLABEL_WIDTH_DEF,
          GUI_BUTTON_HLABEL_HEIGHT_DEF
      );
      gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
      gtk_signal_connect(
          GTK_OBJECT(w), "clicked",
          GTK_SIGNAL_FUNC(FileBrowserCancelCB), fb
      );
      gtk_accel_group_add(
          accelgrp, GDK_Escape, 0, GTK_ACCEL_VISIBLE,
          GTK_OBJECT(w), "clicked"
      );
      gtk_widget_show(w);


      /* Load the icons, the order is important because the index
       * must match the FB_ICON_* values as indices
       */
      if(fb->toplevel != NULL)
      {
          gint i = 0;
          gpointer icon_list[] = FB_ICON_DATA_LIST;
          while(icon_list[i] != NULL)
          {
            FileBrowserIconAppend(
                fb,
                (guint8 **)icon_list[i + 1],    /* XPM data */
                (const gchar *)icon_list[i + 0] /* Description */
            );
            i += 6;
          }
      }

      /* Force the setting of the default list format and get
       * listing
       */
      fb->list_format = -1;
      FileBrowserSetListFormat(fb, FB_LIST_FORMAT_STANDARD);

      return(0);
}

/*
 *    Sets the File Browser's style.
 */
void FileBrowserSetStyle(GtkRcStyle *rc_style)
{
      GtkWidget *w;
      FileBrowser *fb = &file_browser;

      w = fb->toplevel;
      if(w != NULL)
      {
          if(rc_style != NULL)
          {
            gtk_widget_modify_style(w, rc_style);
          }
          else
          {
            rc_style = gtk_rc_style_new();
            gtk_widget_modify_style_recursive(w, rc_style);
            GTK_RC_STYLE_UNREF(rc_style)
          }
      }
}

/*
 *    Sets the File Browser to be a transient for the given
 *    GtkWindow w.
 *
 *    If w is NULL then transient for will be unset.
 */
void FileBrowserSetTransientFor(GtkWidget *w)
{
      FileBrowser *fb = &file_browser;
      if(fb->toplevel != NULL)
      {
          GtkWidget *toplevel = fb->toplevel;

          if(w != NULL)
          {
            if(!GTK_IS_WINDOW(w))
                return;

/* Since the file browser itself has popup windows that may need to be
 * set modal, we cannot set the file browser itself modal.
            gtk_window_set_modal(
                GTK_WINDOW(toplevel), TRUE
            );
 */
            gtk_window_set_transient_for(
                GTK_WINDOW(toplevel), GTK_WINDOW(w)
            );
          }
          else
          {
/*
            gtk_window_set_modal(
                GTK_WINDOW(toplevel), FALSE
            );
 */
            gtk_window_set_transient_for(
                GTK_WINDOW(toplevel), NULL
            );
          }
      }
}

/*
 *    Returns TRUE if currently blocking for query.
 */
gboolean FileBrowserIsQuery(void) 
{
      FileBrowser *fb = &file_browser;

      if(fb->block_loop_level > 0)
          return(TRUE);
      else
          return(FALSE);
}

/*
 *    Ends query if any and returns a not available response.
 */
void FileBrowserBreakQuery(void)
{
      FileBrowser *fb = &file_browser;

      fb->user_response = FALSE;

      /* Break out of an additional blocking loops */
      while(fb->block_loop_level > 0)
      {
          gtk_main_quit();
          fb->block_loop_level--;
      }
      fb->block_loop_level = 0;
}

/*
 *    Returns the File Browser's toplevel GtkWidget.
 */
GtkWidget *FileBrowserGetToplevel(void)
{
      FileBrowser *fb = &file_browser;
      return(fb->toplevel);
}

/*
 *    Clears the lists and unset the location.
 */
void FileBrowserReset(void)
{
      FileBrowser *fb = &file_browser;
      gint i;
      GtkEntry *entry;

      fb->freeze_count++;

#if 0
/* Do not reset the current location */
      /* Unset the current location */
      g_free(fb->cur_location);
      fb->cur_location = NULL;
#endif

      /* Unset the file name entry */
      entry = (GtkEntry *)fb->entry;
      if(entry != NULL)
          gtk_entry_set_text(entry, "");


      /* Clear the locations popup list */
      PUListClear(PUListBoxGetPUList(fb->loc_pulistbox));

      /* Clear the types popup list */
      PUListClear(PUListBoxGetPUList(fb->types_pulistbox));


      /* Clear the objects listing */

      /* Unselect all */
      fb->focus_object = -1;
      g_list_free(fb->selection);
      fb->selection = fb->selection_end = NULL;

      /* Delete all the objects */
      for(i = 0; i < fb->total_objects; i++)
          FileBrowserObjectDestroyCB(fb->object[i]);
      g_free(fb->object);
      fb->object = NULL;
      fb->total_objects = 0;

      /* Reset the objects per row */
      fb->objects_per_row = 0;

      fb->freeze_count--;
}

/*
 *    Maps the file browser and sets up the inital values.
 *
 *    Returns TRUE if a path was selected or FALSE if user canceled.
 *
 *    For most values that are set NULL, the value is left unchanged.
 *    All given values are coppied.
 *
 *    If type is set to NULL however, then the type list on the file
 *    browser will be left empty.
 *
 *    All returned pointer values should be considered statically
 *    allocated. The returned pointer for type_rtn may point to
 *    a structure in the input type list.
 */
gboolean FileBrowserGetResponse(
      const gchar *title,
      const gchar *ok_label, const gchar *cancel_label,
      const gchar *path,
      fb_type_struct **type, gint total_types,
      gchar ***path_rtn, gint *path_total_rtns,
      fb_type_struct **type_rtn
)
{
      GtkWidget *w, *toplevel;
      FileBrowser *fb = &file_browser;

#define RESET_RETURNS   {     \
 if(path_rtn != NULL)         \
  *path_rtn = NULL;           \
 if(path_total_rtns != NULL)  \
  *path_total_rtns = 0;       \
                        \
 if(type_rtn != NULL)         \
  *type_rtn = NULL;           \
}

      /* Do not handle response if already waiting for a response,
       * return with a not available response code
       */
      if(fb->block_loop_level > 0)
      {
          RESET_RETURNS
          return(FALSE);
      }


      /* Reset values */
      fb->user_response = FALSE;

      g_free(fb->cur_type.name);
      g_free(fb->cur_type.ext);
      memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));


      /* Reset the returns */
      RESET_RETURNS


      toplevel = fb->toplevel;

      /* Reget drive paths */
      strlistfree(fb->drive_path, fb->total_drive_paths);
      fb->drive_path = FileBrowserGetDrivePaths(&fb->total_drive_paths);

      /* Set title */
      if(title != NULL)
          gtk_window_set_title(GTK_WINDOW(toplevel), title);

      /* Set OK Button label */
      w = fb->ok_btn_label;
      if((ok_label != NULL) && (w != NULL))
          gtk_label_set_text(GTK_LABEL(w), ok_label);

      /* Set Cancel Button label */
      w = fb->cancel_btn_label;
      if((cancel_label != NULL) && (w != NULL))
          gtk_label_set_text(GTK_LABEL(w), cancel_label);

      /* Set the types list */
      if((type != NULL) && (total_types > 0))
          FileBrowserTypesListSetTypes(
            fb,
            type, total_types
          );


      FileBrowserSetBusy(fb, TRUE);

      /* Map File Browser
       *
       * This needs to be done now in order to allow the proper
       * realizing of sizes before other things can be updated
       */
      FileBrowserMap();
#if 0
/* This is not needed any more since object positions are updated when
 * a "configure_event" signal is received
 */
      while(gtk_events_pending() > 0)
          gtk_main_iteration();
#endif

      /* No initial path specified? */
      if(STRISEMPTY(path))
      {
          /* If no initial path was specified then we need to
           * check if the list was previous cleared (reset), in
           * which case we need to reget the listing
           */
          if(fb->total_objects <= 0)
          {
            FileBrowserListUpdate(fb, NULL);
            FileBrowserLocationsPUListUpdate(fb);
          }
      }
      else
      {
          /* Set the initial location */
          FileBrowserSetLocation(fb, path);
      }

      /* If no location was set then set the initial location as
       * the home directory
       */
      if(STRISEMPTY(fb->cur_location))
          FileBrowserSetLocation(fb, "~");

      /* Set the first object in the list in focus */
      if(fb->total_objects > 0)
      {
          if(fb->focus_object < 0)
          {
            fb->focus_object = 0;
            gtk_widget_queue_draw(fb->list_da);
          }
      }

      FileBrowserSetBusy(fb, FALSE);

      /* Block until user response */
      fb->block_loop_level++;
      gtk_main();

      /* Unmap the File Browser just in case it was not unmapped from
       * any of the callbacks
       */
      FileBrowserUnmap();

      /* Break out of an additional blocking loops */
      while(fb->block_loop_level > 0)
      {
          gtk_main_quit();
          fb->block_loop_level--;
      }
      fb->block_loop_level = 0;


      /* Begin setting returns */

      /* Response path returns */
      if(path_rtn != NULL)
          *path_rtn = fb->selected_path;
      if(path_total_rtns != NULL)
          *path_total_rtns = fb->total_selected_paths;

      /* File type */
      if(type_rtn != NULL)
          *type_rtn = &fb->cur_type;

      return(fb->user_response);
#undef RESET_RETURNS
}


/*
 *    Maps the File Browser.
 */
void FileBrowserMap(void)
{
      FileBrowser *fb = &file_browser;
      GtkWidget *w = fb->toplevel;

      /* Map toplevel */
      gtk_widget_show_raise(w);

      /* Grab focus and default for the list */
      w = fb->list_da;
      gtk_widget_grab_focus(w);
      gtk_widget_grab_default(w);
}

/*
 *    Unmaps the File Browser.
 */
void FileBrowserUnmap(void)
{
      FileBrowser *fb = &file_browser;
      GtkWidget *w = fb->toplevel;
      gtk_widget_hide(w);
}

/*
 *    Shuts down the File Browser.
 */
void FileBrowserShutdown(void)
{
      gint i;
      FileBrowser *fb = &file_browser;

      /* Begin deleting values */

      /* Current file type */
      g_free(fb->cur_type.name);
      g_free(fb->cur_type.ext);
      memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));

      /* Selected paths */
      for(i = 0; i < fb->total_selected_paths; i++)
          g_free(fb->selected_path[i]);
      g_free(fb->selected_path);
      fb->selected_path = NULL;
      fb->total_selected_paths = 0;

      /* Selectioned objects list */
      fb->focus_object = -1;
      g_list_free(fb->selection);
      fb->selection = fb->selection_end = NULL;

      /* Objects */
      for(i = 0; i < fb->total_objects; i++)
          FileBrowserObjectDestroyCB(fb->object[i]);
      g_free(fb->object);
      fb->object = NULL;
      fb->total_objects = 0;
      fb->objects_per_row = 0;

      /* Icons */
      for(i = 0; i < fb->total_icons; i++)
          FileBrowserIconDestroyCB(fb->icon[i]);
      g_free(fb->icon);
      fb->icon = NULL;
      fb->total_icons = 0;

      /* List columns */
      FileBrowserListColumnsClear(fb);

      /* Drive paths */
      strlistfree(fb->drive_path, fb->total_drive_paths);
      fb->drive_path = NULL;
      fb->total_drive_paths = 0;


      /* Break out of any additional blocking loops */
      while(fb->block_loop_level > 0)
      {
          gtk_main_quit();
          fb->block_loop_level--;
      }
      fb->block_loop_level = 0;


      /* Begin destroying widgets */

      GTK_WIDGET_DESTROY(fb->list_menu);
      fb->list_menu = NULL;

      PUListBoxDelete(fb->loc_pulistbox);
      fb->loc_pulistbox = NULL;

      GTK_WIDGET_DESTROY(fb->goto_parent_btn);
      fb->goto_parent_btn = NULL;
      GTK_WIDGET_DESTROY(fb->new_directory_btn);
      fb->new_directory_btn = NULL;
      GTK_WIDGET_DESTROY(fb->rename_btn);
      fb->rename_btn = NULL;
      GTK_WIDGET_DESTROY(fb->refresh_btn);
      fb->refresh_btn = NULL;

      GTK_WIDGET_DESTROY(fb->show_hidden_objects_tb);
      fb->show_hidden_objects_tb = NULL;

      GTK_WIDGET_DESTROY(fb->list_format_standard_tb);
      fb->list_format_standard_tb = NULL;
      GTK_WIDGET_DESTROY(fb->list_format_vertical_tb);
      fb->list_format_vertical_tb = NULL;
      GTK_WIDGET_DESTROY(fb->list_format_vertical_details_tb);
      fb->list_format_vertical_details_tb = NULL;

      GTK_WIDGET_DESTROY(fb->list_header_da);
      fb->list_header_da = NULL;
      GTK_WIDGET_DESTROY(fb->list_da);
      fb->list_da = NULL;
      GTK_WIDGET_DESTROY(fb->list_vsb);
      fb->list_vsb = NULL;
      GTK_WIDGET_DESTROY(fb->list_hsb);
      fb->list_hsb = NULL;

      GTK_WIDGET_DESTROY(fb->entry);
      fb->entry = NULL;
      PUListBoxDelete(fb->types_pulistbox);
      fb->types_pulistbox = NULL;
      GTK_WIDGET_DESTROY(fb->ok_btn);
      fb->ok_btn = NULL;
      GTK_WIDGET_DESTROY(fb->cancel_btn);
      fb->cancel_btn = NULL;

      GTK_WIDGET_DESTROY(fb->main_vbox);
      fb->main_vbox = NULL;
      GTK_WIDGET_DESTROY(fb->toplevel);
      fb->toplevel = NULL;

      GDK_PIXMAP_UNREF(fb->list_pm);
      fb->list_pm = NULL;

      GDK_CURSOR_DESTROY(fb->cur_busy);
      fb->cur_busy = NULL;
      GDK_CURSOR_DESTROY(fb->cur_column_hresize);
      fb->cur_column_hresize = NULL;
      GDK_CURSOR_DESTROY(fb->cur_translate);
      fb->cur_translate = NULL;

      GTK_ACCEL_GROUP_UNREF(fb->accelgrp);
      fb->accelgrp = NULL;


      g_free(fb->home_path);
      fb->home_path = NULL;

      g_free(fb->cur_location);
      fb->cur_location = NULL;

      memset(fb, 0x00, sizeof(FileBrowser));
}


/*
 *    Gets the last location.
 *
 *    If no previous location was set then NULL will be returned.
 */
const gchar *FileBrowserGetLastLocation(void)
{
      return(FileBrowserGetLocation(&file_browser));
}

/*
 *    Show hidden objects.
 */
void FileBrowserShowHiddenObjects(const gboolean show)
{
      FileBrowser *fb = &file_browser;
      FileBrowserSetShowHiddenObjects(fb, show);
}

/*
 *    Sets the list format to standard.
 */
void FileBrowserListStandard(void)
{
      FileBrowser *fb = &file_browser;
      FileBrowserSetListFormat(fb, FB_LIST_FORMAT_STANDARD);
}

/*
 *    Sets the list format to detailed.
 */
void FileBrowserListDetailed(void)
{
      FileBrowser *fb = &file_browser;
      FileBrowserSetListFormat(fb, FB_LIST_FORMAT_VERTICAL_DETAILS);
}


/*
 *    Convience function to allocate a new file browser file extension
 *    type structure and append it to the given list. The given list
 *    will be modified and the index number of the newly allocated
 *    structure will be returned.
 *
 *    Can return -1 on error.
 */
gint FileBrowserTypeListNew(
      fb_type_struct ***list, gint *total,
      const gchar *ext, /* Space separated list of extensions */
      const gchar *name /* Descriptive name */
)
{
      gint n;
      fb_type_struct *fb_type_ptr;


      if((list == NULL) || (total == NULL))
          return(-1);

      n = MAX(*total, 0);
      *total = n + 1;

      /* Allocate more pointers */
      *list = (fb_type_struct **)g_realloc(
          *list,
          (*total) * sizeof(fb_type_struct *)
      );
      if(*list == NULL)
      {
          *total = 0;
          return(-1);
      }

      /* Allocate new structure */
      fb_type_ptr = (fb_type_struct *)g_malloc0(sizeof(fb_type_struct));
      (*list)[n] = fb_type_ptr;
      if(fb_type_ptr == NULL)
      {
          *total = n;
          return(-1);
      }

      /* Set values */
      fb_type_ptr->ext = STRDUP(ext);
      fb_type_ptr->name = STRDUP(name);

      return(n);
}

/*
 *    Deletes the type.
 */
static void FileBrowserTypeDelete(fb_type_struct *t)
{
      if(t == NULL)
          return;

      g_free(t->ext);
      g_free(t->name);
      g_free(t);
}

/*
 *    Deletes the types list.
 */
void FileBrowserDeleteTypeList(
      fb_type_struct **list, const gint total 
)
{
      gint i;

      for(i = 0; i < total; i++)
          FileBrowserTypeDelete(list[i]);

      g_free(list);
}

Generated by  Doxygen 1.6.0   Back to index