/*
** 1998-05-25 -	The amount of code in the main module dealing with dirpanes simply got too big. Dike, dike.
** 1998-06-08 -	Redesigned sorting somewhat. Now, the information used to determine how to sort resides
**		in the configuration structure, not the individual dir pane.
** 1998-08-02 -	Fixed big performance mishap; when changing the sort mode, a full dir reread was done,
**		rather than just a (relatively quick) redisplay.
** 1998-08-08 -	Now finally supports high-speed dragging without losing lines. Great.
** 1999-03-05 -	Massive changes since we now rely on GTK+'s CList widget to handle all selection details.
**		Gives dragging, scrolling, and stuff.
** 1999-03-14 -	Opaque-ified the access to DirRow fields.
** 1999-05-29 -	Added support for symlinks. More controlled and more memory efficient than
**		the old code.
*/

#include "gentoo.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "errors.h"
#include "dpformat.h"
#include "fileutil.h"
#include "userinfo.h"
#include "cmdseq.h"
#include "types.h"
#include "sizeutil.h"
#include "strutil.h"
#include "guiutil.h"
#include "dirhistory.h"
#include "mount.h"
#include "children.h"
#include "colorutil.h"
#include "cfg_gui.h"

#include "dirpane.h"

/* ----------------------------------------------------------------------------------------- */

static DPSort	the_sort;			/* This is used while sorting. */

static GtkStyle	*pane_selected = NULL,		/* The style used on selected pane's column buttons. */
		*focus_style = NULL;			/* For focusing. */

/* ----------------------------------------------------------------------------------------- */

#include "graphics/icon_dirparent.xpm"	/* Graphics for the parent button. */

/* ----------------------------------------------------------------------------------------- */

static void	clear_total_stats(DirPane *dp);
static void	clear_selection_stats(SelInfo *si);
static void	update_selection_stats(DirPane *dp);

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-24 -	Given a DirPane which is considered the "source" of some operation (this
**		will generally be the currently selected pane), compute the destination.
** 1998-07-29 -	Moved into the DirPane module and made generally available, since that
**		made so much sense (was private in commands module?).
*/
DirPane * dp_mirror(MainInfo *min, DirPane *dp)
{
	if(dp == &min->gui->pane[0])
		return &min->gui->pane[1];
	else if(dp == &min->gui->pane[1])
		return &min->gui->pane[0];
	return NULL;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-05 -	Get the full name, with path, for row number <row> of <dp>. Trivial, but
**		saves a couple of lines here and there elsewhere in the program.
** 1999-01-20 -	Now tries to avoid starting the name with two slashes for root entries.
*/
const gchar * dp_full_name(DirPane *dp, gint row)
{
	static gchar	buf[PATH_MAX];

	if((dp->dir.path[0] == '/') && (dp->dir.path[1] == '\0'))	/* Faster than strcmp(path, "/")? ;^) */
		g_snprintf(buf, sizeof buf, "/%s", DP_ROW_NAME(&dp->dir.line[row]));
	else
		g_snprintf(buf, sizeof buf, "%s/%s", dp->dir.path, DP_ROW_NAME(&dp->dir.line[row]));
	return buf;
}


/* 1999-02-23 -	Get the name of a row, but quoted and with any embedded quotes
**		escaped by backslashes. Very handy when evaluating {f}-codes...
**		If <path> is TRUE, the path is included as well.
*/
const gchar * dp_name_quoted(DirPane *dp, gint row, guint path)
{
	static gchar	buf[2 * PATH_MAX];
	const gchar	*fn, *ptr;
	gchar		*put = buf;

	fn = path ? dp_full_name(dp, row) : DP_ROW_NAME(&dp->dir.line[row]);
	*put++ = '"';
	for(ptr = fn; *ptr; ptr++)
	{
		if(*ptr == '"')
			*put++ = '\\';
		*put++ = *ptr;
	}
	*put++ = '"';
	*put = '\0';

	return buf;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-15 -	This handler gets run when user clicks the "up" button next to path entry. */
static gint evt_pt_clicked(GtkWidget *wid, gpointer user)
{
	DirPane	*dp = (DirPane *) user;

	csq_execute(dp->main, dp->index == 0 ? "ActivateLeft" : "ActivateRight");
	csq_execute(dp->main, "DirParent");
	return TRUE;
}

/* 1998-05-24 -	Handle press of <Return> in path entry widget. Now also refocuses,
**		in order for the main widget keypress handler to get keyboard rights.
*/
static gint evt_new_path(GtkWidget *wid, gpointer data)
{
	DirPane	*dp = (DirPane *) data;

	dp_enter_dir(dp, gtk_entry_get_text(GTK_ENTRY(wid)));
	gtk_widget_grab_focus(GTK_WIDGET(dp->main->gui->top));
	dp_set(dp);
	if(strcmp(dp->dir.path, gtk_entry_get_text(GTK_ENTRY(wid))))	/* No set? */
		gtk_entry_set_text(GTK_ENTRY(wid), dp->dir.path);	/* Then restore entry contents. */

	return TRUE;
}

/* 1998-05-27 -	I had forgotten about the possibility for users to click in the path
**		entry widget, and by doing so circumventing my clever (hairy) flag
**		system. This remedies that.
** 1998-05-29 -	Changed polarity of operation. Was broken. Sloppy me.
*/
static gint evt_path_focus(GtkWidget *wid, GdkEventFocus *ev, gpointer data)
{
	DirPane	*dp = (DirPane *) data;

	kbd_context_detach(dp->main->gui->kbd_ctx, GTK_WINDOW(dp->main->gui->window));
	dp_set(dp);

	return TRUE;
}

static gint evt_path_unfocus(GtkWidget *wid, GdkEventFocus *ev, gpointer data)
{
	DirPane	*dp = data;

	kbd_context_attach(dp->main->gui->kbd_ctx, GTK_WINDOW(dp->main->gui->window));

	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

static gint evt_path_history(GtkWidget *wid, gpointer data)
{
	DirPane	*dp = (DirPane *) data;

	return evt_new_path(GTK_COMBO(dp->path)->entry, data);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-12-19 -	The tiny little "hide" button was clicked. Act. */
static gint evt_hide_clicked(GtkWidget *wid, gpointer data)
{
	DirPane	*dp = (DirPane *) data;

	dp_set(dp);
	dp->main->cfg.dp_format[dp->index].hide_allowed = GTK_TOGGLE_BUTTON(wid)->active;
	csq_execute(dp->main, "DirRescan");
	return 1;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-10 -	Nestable freeze. Very useful; ALWAYS use these rather than direct
**		freezing/thawing on the dp's clist!
** 1999-03-04 -	GTK+ 1.2.0 does the right thing, making this function even simpler.
*/
void dp_freeze(DirPane *dp)
{
	gtk_clist_freeze(GTK_CLIST(dp->list));
}

/* 1998-09-10 -	Nestable thaw. Always use it!
** 1998-09-19 -	Umm... This function was just proved to be affecting (clearing) errno.
**		That fscks up the error handling all over the place, so I added some
**		local save/restore of it. I can't claim to understand any of it, though.
** 1999-03-04 -	Did 1.2.0 simplifications.
*/
void dp_thaw(DirPane *dp)
{
	int	old_errno = errno;

	gtk_clist_thaw(GTK_CLIST(dp->list));
	errno = old_errno;
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-03-06 -	The given <dp> has been double-clicked. Time to execute some fun action,
**		namely the Default for the style of the clicked row.
*/
void dp_activate(DirPane *dp)
{
	err_clear(dp->main);
	csq_execute(dp->main, "FileAction");
	if(chd_get_running(NULL) == NULL)	/* If command sequence finished, reset. Else keep around. */
		dp->dbclk_row = -1;
	err_show(dp->main);
}

/* 1999-05-13 -	Activate ("double click") on <row> in <dp>. Handy for use by the focusing
**		module.
*/
void dp_activate_row(DirPane *dp, gint row)
{
	if(row != -1)
	{
		dp->dbclk_row = row;
		dp_activate(dp);
	}
}

/* 1999-03-04 -	Rewritten. Now very much simpler. :) */
gint dp_select(DirPane *dp, gint row)
{
	if(g_list_find(GTK_CLIST(dp->list)->selection, GINT_TO_POINTER(row)) == NULL)
	{
		gtk_clist_select_row(GTK_CLIST(dp->list), row, -1);
		return 1;
	}
	return 0;
}

/* 1999-03-04 -	Select all rows. Handy for the SelectAll command. Since selection management
**		is now largely done by the GtkCList widget for us, this is not rocket science.
*/
void dp_select_all(DirPane *dp)
{
	gtk_clist_select_all(GTK_CLIST(dp->list));
}

/* 1999-03-04 -	Rewritten in a lot simpler way. */
gint dp_unselect(DirPane *dp, gint row)
{
	if(row == dp->dbclk_row)
		dp->dbclk_row = -1;
	else if(g_list_find(GTK_CLIST(dp->list)->selection, GINT_TO_POINTER(row)) != NULL)
	{
		gtk_clist_unselect_row(GTK_CLIST(dp->list), row, -1);
		return 1;
	}
	return 0;
}

/* 1999-03-04 -	Unselect all rows. Real simple. */
void dp_unselect_all(DirPane *dp)
{
	gtk_clist_unselect_all(GTK_CLIST(dp->list));
}

/* 1998-06-04 -	Toggle the given <row>. Very simple, thanks to the return
**		values provided by the select/unselect functions above.
*/
gint dp_toggle(DirPane *dp, gint row)
{
	if(!dp_select(dp, row))
		dp_unselect(dp, row);
	return 1;
}

/* 1998-09-06 -	Select (er, toggle) all rows having the same suffix as <row>, thus allowing
**		some easy going selection on stuff for which you don't have a proper type
**		set up. Assumes that a period ('.') separates the suffix from the
**		rest of the filename.
*/
static void suffix_select(DirPane *dp, gint row)
{
	const char	*suff;
	guint		i;

	if((suff = str_strcaserchr(DP_ROW_NAME(&dp->dir.line[row]), '.')) != NULL)
	{
		for(i = 0; i < dp->dir.num_lines; i++)
		{
			if(str_has_suffix(DP_ROW_NAME(&dp->dir.line[i]), suff))
				dp_toggle(dp, i);
		}
	}
}

/* 1998-08-26 -	A feature occured to me while walking home from the bus: it should be
**		possible to select (and deselect) all lines of a certain type easily.
**		Here's the solution: hold down meta (left Alt on my keyboard) while
**		clicking, and all lines having the type of the clicked line will toggle
**		their selected state. Handy.
** 1998-08-30 -	Extended. Now does either a toggle (default), or if SHIFT is detected
**		a forced select. Hold down CTRL to force unselection. SHIFT wins over
**		CTRL.
*/
static void dp_meta_action(DirPane *dp, FType *type, guint state)
{
	guint	i;
	gint	(*func)(DirPane *, gint) = dp_toggle;

	func = dp_toggle;
	if(state & GDK_SHIFT_MASK)
		func = dp_select;
	else if(state & GDK_CONTROL_MASK)
		func = dp_unselect;
	for(i = 0; i < dp->dir.num_lines; i++)
	{
		if(DP_ROW_TYPE(&dp->dir.line[i]) == type)
			func(dp, i);
	}
}

/* 1999-03-04 -	A row in the pane was selected. Update statistics. */
static gint evt_dirpane_select_row(GtkWidget *wid, gint row, gint col, GdkEventButton *ev, gpointer data)
{
	DirPane	*dp = data;
	DirRow	*dr = gtk_clist_get_row_data(GTK_CLIST(wid), row);

	if(S_ISDIR(DP_ROW_LSTAT(dr).st_mode))
		dp->dir.sel.num_dirs++;
	else
		dp->dir.sel.num_files++;
	dp->dir.sel.num_bytes  += DP_ROW_LSTAT(dr).st_size;
	dp->dir.sel.num_blocks += DP_ROW_LSTAT(dr).st_blocks;
	dp_show_stats(data);

	if(dp->dbclk_row == row)
		dp_activate(dp);

	return FALSE;
}

/* 1999-03-04 -	A row has been unselected, so we need to update our statistics.
** 1999-03-06 -	Now does the double-click "activation". When we arrive here, the double-
**		clicked row is no longer selected (dp_get_selection() will not include it),
**		which is very important, for example for the directory history.
*/
static gint evt_dirpane_unselect_row(GtkWidget *wid, gint row, gint col, GdkEventButton *ev, gpointer data)
{
	DirPane	*dp = data;

	update_selection_stats(dp);
	dp_show_stats(dp);

	if(dp->dbclk_row >= 0)		/* Did a double-click occur resently? */
		dp_activate(dp);

	return FALSE;
}

/* 1999-03-05 -	Update the state of the GDK_CONTROL_MASK bitmask in <state>, depending on how
**		the given pane has its ctrl_mode formatting mode set.
*/
static guint update_ctrl_state(DirPane *dp, guint state)
{
	CtrlMode	mode = dp->main->cfg.dp_format[dp->index].ctrl_mode;

	switch(mode)
	{
		case CTM_IGNORE:
			break;
		case CTM_SET:
			state |= GDK_CONTROL_MASK;
			break;
		case CTM_RESET:
			state &= ~GDK_CONTROL_MASK;
			break;
		case CTM_TOGGLE:
			state ^= GDK_CONTROL_MASK;
			break;
	}
	return state;
}

/* 1998-06-18 -	This handles the situation when a user clicks on a row in a
**		directory pane. Might envoke action on files/dirs.
** 1998-06-04 -	Added support for a little menu popping up when the right
**		button is pressed. First it changes to the given pane.
** 1998-06-17 -	Added shift-sensing to extend the current selection. Not so
**		crucial in my mind, since dragging is so supported. But, nice
**		never the less (and somewhat standard, so people might expect it).
** 1998-06-17 -	Added control-sensing to "extend" the current UNselection. Cool?
** 1999-03-05 -	Simplified since we now use GTK+'s built-in CList selection.
*/
static gint evt_dirpane_press(GtkWidget *wid, GdkEventButton *event, gpointer user)
{
	gint		ret, row, col;
	DirPane		*dp = user;
	static DirPane	*last_dp = NULL;

	last_dp = dp;
	dp_set(dp);

	gtk_widget_grab_focus(GTK_WIDGET(dp->main->gui->top));

	/* Hard coding is the order of the day. Middle button does a "parent". */
	if(event->button == 2)
	{
		csq_execute(dp->main, "DirParent");
		return TRUE;
	}

	/* If it's the right button, show a pop-up menu. It's NOT context-sensitive. */
	if(event->button == 3)
	{
		dp_set(dp);
		gtk_menu_popup(GTK_MENU(dp->main->gui->dpmenu), NULL, NULL, NULL, NULL, event->button, event->time);
		return TRUE;
	}

	ret = gtk_clist_get_selection_info(GTK_CLIST(wid), (gint) event->x, (gint) event->y, &row, &col);

	if(ret && (guint) row < dp->dir.num_lines)
	{
		if((event->state & GDK_MOD1_MASK) ||
		   (event->type == GDK_2BUTTON_PRESS && (row == dp->last_row2)))
		{
			if(event->state & GDK_MOD1_MASK)
			{
				if((event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
					suffix_select(dp, row);
				else
					dp_meta_action(dp, DP_ROW_TYPE(&dp->dir.line[row]), event->state);
			}
			else
				dp->dbclk_row = row;	/* Remember this until the next *unselection*! */
			gtk_signal_emit_stop_by_name(GTK_OBJECT(dp->list), "button_press_event");	/* Stop the signal. */
		}
		else
			event->state = update_ctrl_state(dp, event->state);

		dp->last_row2 = dp->last_row;
		dp->last_row = row;
		return FALSE;
	}
	dp->last_row = dp->last_row2 = -1;

	return FALSE;
}

static void evt_dirpane_col_click(GtkWidget *wid, gint column, gpointer user)
{
	DirPane	*dp = user;
	DPSort	*sort;

	if(!dp_set(dp))		/* Only change sort if click didn't cause pane change. */
	{
		DHSel	*sel;

		sort = &dp->main->cfg.dp_format[dp->index].sort;
		if(column == sort->column)
			sort->invert ^= 1;	/* Clicking on active title toggles inversion. */
		else
			sort->column = column;	/* Otherwise it sets a new sorting column. */

		sel = dph_dirsel_create(dp);
		clear_selection_stats(&dp->dir.sel);
		dp_resort(dp);
		dp_redisplay(dp);
		dph_dirsel_apply(dp, sel);
		dph_dirsel_destroy(sel);
	}
}

void evt_dirpane_menu(GtkWidget *wid, gpointer user)
{
	MainInfo	*min;

	if((min = gtk_object_get_user_data(GTK_OBJECT(wid))) != NULL)
		csq_execute(min, (const gchar *) user);
}

/* 1999-05-15 -	User resized a column. Update format. I hope this is a new signal... */
static void evt_dirpane_resize_column(GtkWidget *wid, gint column, gint width, gpointer user)
{
	DirPane	*dp = user;

	dp->main->cfg.dp_format[dp->index].format[column].width = width;
	cfg_modified_set(dp->main);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-29 -	Compute a result for qsort() comparison function, taking care
**		to keep directories on top of files at all times.
** 1998-06-07 -	Now implements all the sorting modes offered by Opus, meaning
**		directories can be on first, last, or mixed with the files. The
**		inversion is also done here, making it a lot more "true" than
**		the old hack.
*/
static int qsort_result(int s, DirRow *da, DirRow *db)
{
	int	isda, isdb;

	if(the_sort.invert)
		s = -s;

	if(DP_ROW_GET_STAT(da) != NULL)
		isda = S_ISDIR(DP_ROW_STAT(da).st_mode);
	else
		isda = S_ISDIR(DP_ROW_LSTAT(da).st_mode);

	if(DP_ROW_GET_STAT(db) != NULL)
		isdb = S_ISDIR(DP_ROW_STAT(db).st_mode);
	else
		isdb = S_ISDIR(DP_ROW_LSTAT(db).st_mode);

	switch(the_sort.mode)
	{
		case DPS_DIRS_FIRST:
			if(isda == isdb)
				return s;
			else if(isda)
				return -1;
			return 1;
		case DPS_DIRS_LAST:
			if(isda == isdb)
				return s;
			else if(isdb)
				return -1;
			return 1;
		case DPS_DIRS_MIXED:
			return s;
	}
	return s;		/* I bet this doesn't ever run. Gcc doesn't. */
}

/* 1998-05-29 -	Compare two directory entries by name.
** 1998-06-29 -	Added support for being case-insensitive. This might seem woefully inefficient (it does to
**		me!) but some actual benchmarking showed that the decision-making makes very little difference
**		(on the order of thousands of a second for /dev, with ~1300 entries...).
*/
static int qsort_cmp_name(const void *ap, const void *bp)
{
	DirRow	*da = (DirRow *) ap, *db = (DirRow *) bp;
	int	sd;

	if(the_sort.nocase)
		sd = str_strcasecmp(DP_ROW_NAME(da), DP_ROW_NAME(db));
	else
		sd = strcmp(DP_ROW_NAME(da), DP_ROW_NAME(db));
	return qsort_result(sd, da, db);
}

/* 1998-05-29 -	A hairy macro, which can be used to generate sorting data from two dirrows, by numerically
**		comparing one field in the stat() datas. The ARG argument should be the name of the field
**		to compare, e.g. st_size, st_mode etc.
*/
#define	QBODY(ARG)	{DirRow *da = (DirRow *) ap, *db = (DirRow *) bp;\
			int sd;\
			\
			sd = (DP_ROW_LSTAT(da). ARG) - (DP_ROW_LSTAT(db). ARG);\
			return qsort_result(sd, da, db);\
			}

static int qsort_cmp_size(const void *ap, const void *bp)
{
	QBODY(st_size);
}

static int qsort_cmp_blocks(const void *ap, const void *bp)
{
	QBODY(st_blocks);
}

/* 1998-09-08 -	The new "IQ size" content sorts just as if it showed the true size. */
static int qsort_cmp_iqsize(const void *ap, const void *bp)
{
	return qsort_cmp_size(ap, bp);
}

static int qsort_cmp_modeoct(const void *ap, const void *bp)
{
	QBODY(st_mode);
}

static int qsort_cmp_nlink(const void *ap, const void *bp)
{
	QBODY(st_nlink);
}

static int qsort_cmp_uiddec(const void *ap, const void *bp)
{
	QBODY(st_uid);
}

static int qsort_cmp_giddec(const void *ap, const void *bp)
{
	QBODY(st_gid);
}

static int qsort_cmp_dev(const void *ap, const void *bp)
{
	QBODY(st_rdev);
}

static int qsort_cmp_devmaj(const void *ap, const void *bp)
{
	QBODY(st_rdev >> 8);
}

static int qsort_cmp_devmin(const void *ap, const void *bp)
{
	QBODY(st_rdev & 0xFF);
}

static int qsort_cmp_atime(const void *ap, const void *bp)
{
/*	QBODY(st_atim.tv_sec);*/
	QBODY(st_atime);
}

static int qsort_cmp_mtime(const void *ap, const void *bp)
{
/*	QBODY(st_mtim.tv_sec);*/
	QBODY(st_mtime);
}

static int qsort_cmp_ctime(const void *ap, const void *bp)
{
/*	QBODY(st_ctim.tv_sec);*/
	QBODY(st_ctime);
}

/* 1998-08-02 -	Compare type names.
** 1998-09-04 -	Added unexpected coolness; falls back to name comparison if types are equal.
*/
static int qsort_cmp_type(const void *ap, const void *bp)
{
	DirRow	*da = (DirRow *) ap, *db = (DirRow *) bp;
	int	sd;

	if(the_sort.nocase)
		sd = str_strcasecmp(DP_ROW_TYPE(da)->name, DP_ROW_TYPE(db)->name);
	else
		sd = strcmp(DP_ROW_TYPE(da)->name, DP_ROW_TYPE(db)->name);
	if(sd == 0)
		return qsort_cmp_name(ap, bp);
	return qsort_result(sd, da, db);
}

/* 1998-09-13 -	Here's a sorting mode that was simply lacking. It is bound to the icons
**		column. This feels very slow and generally bad, but I haven't figured
**		out a way of doing this bottom-up (or if that's even possible).
** 1998-09-19 -	Rewritten. I now think it might even work. Think of this as a big slow
**		strcmp() over segmented strings represented as linked lists, and you'll
**		be fine. :)
*/
static int qsort_cmp_style(const void *ap, const void *bp)
{
#if 0
	DirRow	*da = (DirRow *) ap, *db = (DirRow *) bp;
	FStyle	*sta = DP_ROW_TYPE(da)->style, *stb = DP_ROW_TYPE(db)->style;
	int	(*scmp)(const char *, const char *) = the_sort.nocase ? str_strcasecmp : strcmp;
	int	sd = -1;
	GSList	*sla, *slb, *ia, *ib;

	/* Get rid of the case that both styles are equal as fast as possible. */
	if(sta == stb)
		return qsort_result(scmp(DP_ROW_NAME(da), DP_ROW_NAME(db)), da, db);

	if((sla = get_branch(sta)) != NULL)
	{
		if((slb = get_branch(stb)) != NULL)
		{
			for(ia = sla, ib = slb; (ia != NULL && ib != NULL) && (sd = strcmp((char *) ia->data, (char *) ib->data)) == 0;)
			{
				ia = g_slist_next(ia);
				ib = g_slist_next(ib);
			}
			if(ia == NULL || ib == NULL)
				sd = (ia == NULL) ? -1 : 1;
			g_slist_free(slb);
		}
		g_slist_free(sla);
	}
#endif

	return qsort_result(stl_style_compare_hierarchy(DP_ROW_TYPE((DirRow *) ap)->style, DP_ROW_TYPE((DirRow *) bp)->style),
			    (void *) ap, (void *) bp);
}

typedef int	(*sorter)(const void *ap, const void *bp);

/* 1998-05-29 -	Select a sorting function based on the given pane. */
static sorter choose_sorter(DirPane *dp)
{
	int	index = dp->index;
	int	column = (int) dp->main->cfg.dp_format[index].sort.column;

	switch(dp->main->cfg.dp_format[index].format[column].content)
	{
		case DPC_NAME:
			return qsort_cmp_name;
		case DPC_SIZE:
			return qsort_cmp_size;
		case DPC_BLOCKS:
			return qsort_cmp_blocks;
		case DPC_IQSIZE:
			return qsort_cmp_iqsize;
		case DPC_MODENUM:
		case DPC_MODESTR:
			return qsort_cmp_modeoct;
		case DPC_NLINK:
			return qsort_cmp_nlink;
		case DPC_UIDNUM:
		case DPC_UIDSTR:
			return qsort_cmp_uiddec;
		case DPC_GIDNUM:
		case DPC_GIDSTR:
			return qsort_cmp_giddec;
		case DPC_DEVICE:
			return qsort_cmp_dev;
		case DPC_DEVMAJ:
			return qsort_cmp_devmaj;
		case DPC_DEVMIN:
			return qsort_cmp_devmin;
		case DPC_ATIME:
			return qsort_cmp_atime;
		case DPC_MTIME:
			return qsort_cmp_mtime;
		case DPC_CTIME:
			return qsort_cmp_ctime;
		case DPC_TYPE:
			return qsort_cmp_type;
		case DPC_ICON:
			return qsort_cmp_style;
		case DPC_NUM_TYPES:
			fprintf(stderr, "DIRPANE: Illegal column content (%d:%d)\n", dp->index, column);
			return NULL;
	}
	return qsort_cmp_name;
}

/* 1999-04-27 -	Resort the rows of <dp>, according to its format's sort info. Does not cause
**		a redisplay; call dp_redisplay() for that.
** 1999-05-30 -	Fixed a BIG problem: resorting messed up the stat->lstat connections on non-link
**		rows. Very, very bad.
*/
void dp_resort(DirPane *dp)
{
	guint	i;

	/* NULL out any non-link stat fields, to avoid breaking links by resorting. */
	for(i = 0; i < dp->dir.num_lines; i++)
	{
		if(DP_ROW_GET_STAT(dp->dir.line + i) == &DP_ROW_LSTAT(dp->dir.line + i))
			DP_ROW_SET_STAT(dp->dir.line + i, NULL);
	}

	the_sort = dp->main->cfg.dp_format[dp->index].sort;	/* Must be accessible by qsort() callbacks. */
	qsort(dp->dir.line, dp->dir.num_lines, sizeof *dp->dir.line, choose_sorter(dp));

	/* Now re-connect any NULL stat links, thus making them point at the lstat fields again. */
	for(i = 0; i < dp->dir.num_lines; i++)
	{
		if(DP_ROW_GET_STAT(dp->dir.line + i) == NULL)
			DP_ROW_SET_STAT(dp->dir.line + i, &DP_ROW_LSTAT(dp->dir.line + i));
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-04-27 -	Redisplay given pane. Will totally clear the GtkCList widget, and reformat
**		all row data into it. Does *not* resort the pane's contents; use (the new)
**		dp_resort() function for that. Also does *not* preserve the set of selected
**		rows or the vertical position; use dp_redisplay_preserve() for that.
*/
void dp_redisplay(DirPane *dp)
{
	gchar		buffer[DP_COLUMNS_MAX][2 * PATH_MAX];
	gchar		*buf[DP_COLUMNS_MAX];
	gint		row;
	guint		i, j;

	dp_freeze(dp);						/* Reduce ugly flicker. */
	gtk_clist_clear(dp->list);				/* Remove any old entries. */

	for(i = 0; i < sizeof buf / sizeof buf[0]; i++)
		buf[i] = &buffer[i][0];
	for(i = 0; i < dp->dir.num_lines; i++)
	{
		for(j = 0; j < dp->main->cfg.dp_format[dp->index].num_columns; j++)
			dpf_format_column(dp->main, dp, &dp->dir.line[i], j, buf[j]);
		row = gtk_clist_append(dp->list, buf);
		gtk_clist_set_row_data(GTK_CLIST(dp->list), row, &dp->dir.line[i]);
		dpf_post_format_row(dp->main, dp, row);
	}
	dp_thaw(dp);
}

/* 1999-04-27 -	Redisplay given pane, keeping both the selected set and the vertical position.
**		This is the one to use in most cases.
*/
void dp_redisplay_preserve(DirPane *dp)
{
	DHSel	*sel;

	sel = dph_dirsel_create(dp);
	dp_redisplay(dp);
	dph_dirsel_apply(dp, sel);
	dph_dirsel_destroy(sel);
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-05-10 -	Unfocus the currently focused row of <dp>, if any. */
void dp_unfocus(DirPane *dp)
{
	if(dp->focus_row >= 0)
	{
		gtk_clist_set_row_style(dp->list, dp->focus_row, NULL);
		dp->old_focus_row = dp->focus_row;
		dp->focus_row = -1;
	}
}

/* 1999-05-10 -	Attempt to focus the <row>'th row of <dp>. If there was a previous
**		row focused, unfocus it first.
*/
void dp_focus(DirPane *dp, gint row)
{
	if(focus_style == NULL)
	{
		focus_style = gtk_style_copy(gtk_widget_get_style(GTK_WIDGET(dp->list)));

		focus_style->base[GTK_STATE_NORMAL] = dp->main->cfg.dp_colors.color[DPCOL_FOCUS_UNSELECTED];
		focus_style->bg[GTK_STATE_SELECTED] = dp->main->cfg.dp_colors.color[DPCOL_FOCUS_SELECTED];
	}

	if(row < 0)
		row = 0;
	else if(row >= dp->dir.num_lines)
		row = dp->dir.num_lines - 1;

	/* Bring the focused row into view, minimizing disruption while doing so. */
	if(gtk_clist_row_is_visible(dp->list, row) != GTK_VISIBILITY_FULL)
	{
		if((dp->focus_row != -1))		/* Is there already a focused row? */
		{
			gint	delta = row - dp->focus_row;

			if(ABS(delta) == 1)
				gtk_clist_moveto(dp->list, row, -1, delta < 0 ? 0.0 : 1.0, -1);
			else
				gtk_clist_moveto(dp->list, row, -1, 0.5, 0.5);
		}
		else
			gtk_clist_moveto(dp->list, row, -1, 0.5, 0.5);	/* If no previous focus, center the new one. */
	}
	dp_unfocus(dp);
	gtk_clist_set_row_style(dp->list, row, focus_style);
	dp->focus_row = row;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-25 -	Moved this old workhorse into the new dirpane module, and discovered that
**		it wasn't commented. Lazy me. Also modified its prototype, to take the
**		DirPane to display rather than figuring it out from MainInfo.
*/
void dp_show_stats(DirPane *dp)
{
	gchar	buf[128], selbuf[32] = "", totbuf[32] = "", *ptr = buf;
	MainInfo *min;
	SelInfo	*sel;

	if((min = dp->main) != NULL)
	{
		sel = &dp->dir.sel;

		ptr += g_snprintf(ptr, sizeof buf, "%d/%d dirs, %d/%d files", sel->num_dirs, dp->dir.tot_dirs,
				  sel->num_files, dp->dir.tot_files);
		sze_put_size64(sel->num_bytes, selbuf, sizeof selbuf, SZE_AUTO);
		sze_put_size64(dp->dir.tot_bytes, totbuf, sizeof totbuf, SZE_AUTO);
		ptr += sprintf(ptr, " (%s/%s, %Lu/%Lu blocks)", selbuf, totbuf, sel->num_blocks, dp->dir.tot_blocks);
		if(dp->dir.fs.valid)
		{
			gchar	freebuf[32];

			sze_put_size64((guint64) dp->dir.fs.stat.f_bsize * (guint64) dp->dir.fs.stat.f_bavail, freebuf, sizeof freebuf, SZE_AUTO);
			ptr += sprintf(ptr, ", %s free", freebuf);
		}
		gtk_label_set_text(min->gui->top, buf);
	}
}

/* 1998-09-06 -	Set background color of all <dp>'s column buttons. If <col> is NULL,
**		the default background color (actually, the entire default style) will be used.
*/
static void dp_set_col_color(DirPane *dp, GdkColor *col)
{
	guint	i;

	if(dp != NULL)
	{
		if(col == NULL)
		{
			for(i = 0; i < GTK_CLIST(dp->list)->columns; i++)
				col_restore_style(GTK_CLIST(dp->list)->column[i].button);
		}
		else
		{
			if(pane_selected == NULL)
				pane_selected = col_make_style(gtk_widget_get_style(GTK_WIDGET(dp->list)), NULL, col);
			if(pane_selected != NULL)
			{
				for(i = 0; i < GTK_CLIST(dp->list)->columns; i++)
					gtk_widget_set_style(GTK_CLIST(dp->list)->column[i].button, pane_selected);
			}
		}
	}
}

/* 1998-05-18 -	Change the 'current' dirpane.
** 1998-07-11 -	After complaints from an early tester, I poke around a little here.
**		Now modifies shadow type of both clist and surrounding frame.
** 1998-09-06 -	Since everybody hated the frame but noone dared saying so (now *that's* a
**		great rationalizer if you ever need one!), I changed it again. Now uses
**		the style of the column header buttons. Very Opus.
** 1998-09-19 -	Now returns TRUE if the setting was to a new pane, FALSE otherwise.
*/
gboolean dp_set(DirPane *dp)
{
	MainInfo	*min;

	if(dp != NULL)
	{
		min = dp->main;

		if(min->gui->cur_dir != NULL)
		{
			if(dp == min->gui->cur_dir)
				return FALSE;
			dp_unfocus(min->gui->cur_dir);
			dp_set_col_color(min->gui->cur_dir, NULL);
		}
		min->gui->cur_dir = dp;
		dp_set_col_color(min->gui->cur_dir, &dp->main->cfg.dp_colors.color[DPCOL_PANE_SELECTED]);
		dp_show_stats(dp);
		if(dp->old_focus_row >= 0)
			dp_focus(dp, dp->old_focus_row);
	}
	return TRUE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-03-05 -	Answer whether given pane has one or more rows selected or not. */
gint dp_has_selection(DirPane *dp)
{
	return (dp->dbclk_row != -1) || (dp->focus_row != -1) || (GTK_CLIST(dp->list)->selection != NULL);
}

/* 1999-03-06 -	Answer whether given <row> is currently selected. A bit more efficient
**		than getting the entire list through dp_get_selection(), but only if
**		you're not going to iterate the selection anyway.
*/
gint dp_is_selected(DirPane *dp, DirRow *row)
{
	if((dp != NULL) && (row != NULL))
		return g_list_find(GTK_CLIST(dp->list)->selection, GINT_TO_POINTER(DP_ROW_INDEX(dp, row))) != NULL;
	return 0;
}

/* 1999-03-15 -	Compare two rows based on their indexes in the directory. Assmes their addresses
**		are comparable, which they really should be.
*/
static gint compare_rows(gconstpointer a, gconstpointer b)
{
	return (GPOINTER_TO_UINT(a) - GPOINTER_TO_UINT(b));
}

/* 1999-03-04 -	Return a GSList of selected rows. Each item's data member is a DirRow.
**		Knows how to deal with a double click, too. This is going to be the new
**		interface for all commands.
** 1999-03-15 -	Now sorts the rows in address order, since otherwise the selection retains
**		the order in which it was done by the user, and that is simply confusing.
*/
GSList * dp_get_selection(DirPane *dp)
{
	GList	*iter;
	GSList	*sel = NULL;

	if(dp->dbclk_row != -1)
		sel = g_slist_append(sel, gtk_clist_get_row_data(GTK_CLIST(dp->list), dp->dbclk_row));
	else
		for(iter = GTK_CLIST(dp->list)->selection; iter != NULL; iter = g_list_next(iter))
			sel = g_slist_insert_sorted(sel, gtk_clist_get_row_data(GTK_CLIST(dp->list), GPOINTER_TO_INT(iter->data)),
							compare_rows);
	return sel;
}

/* 1999-03-06 -	Get the "full" selection, regardless of whether there's a double clicked row or not.
**		This should only be used if you really know what you're doing, and never by actual
**		commands (which need the double-click support).
*/
GSList * dp_get_selection_full(DirPane *dp)
{
	GList	*iter;
	GSList	*sel = NULL;

	for(iter = GTK_CLIST(dp->list)->selection; iter != NULL; iter = g_list_next(iter))
		sel = g_slist_append(sel, gtk_clist_get_row_data(GTK_CLIST(dp->list), GPOINTER_TO_INT(iter->data)));
	return sel;
}

#if 0		/* dp_print_selection() */
/* 1999-03-05 -	A little routine handy for debugging. */
void dp_print_selection(DirPane *dp, char *tag)
{
	GList	*iter;

	fprintf(stderr, "[%10s] Selection: ", tag);
	for(iter = GTK_CLIST(dp->list)->selection; iter != NULL; iter = g_list_next(iter))
		fprintf(stderr, "'%d' ", GPOINTER_TO_INT(iter->data));
	fprintf(stderr, "\n");
}
#endif

/* 1999-03-04 -	Free a selection list, handy when you're done traversing it. */
void dp_free_selection(GSList *sel)
{
	if(sel != NULL)
		g_slist_free(sel);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-25 -	Count entries as they are read in. */
static void statistics_add_entry(DirPane *dp, struct stat *stat)
{
	if(S_ISDIR(stat->st_mode))
		dp->dir.tot_dirs++;
	else
		dp->dir.tot_files++;
	dp->dir.tot_bytes  += stat->st_size;
	dp->dir.tot_blocks += stat->st_blocks;
}

/* 1999-04-08 -	Clear the selected statistics. */
static void clear_selection_stats(SelInfo *sel)
{
	if(sel != NULL)
	{
		sel->num_dirs = 0;
		sel->num_files = 0;
		sel->num_bytes = 0;
		sel->num_blocks = 0;
	}
}

/* 1999-04-09 -	Clear the total statistics fields for <dp>. */
static void clear_total_stats(DirPane *dp)
{
	dp->dir.tot_files = dp->dir.tot_dirs = 0;
	dp->dir.tot_bytes = dp->dir.tot_blocks = 0;
}

/* 1999-01-03 -	Clear the statistics for <dp>. Useful when the pane's path is about to change. */
static void clear_stats(DirPane *dp)
{
	dp->dir.num_lines = 0;

	clear_total_stats(dp);
	clear_selection_stats(&dp->dir.sel);
	dp->last_row = dp->last_row2 = -1;
}

/* 1999-04-08 -	Update the selection statistics for <dp>, by simply flushing them and recomputing from
**		scratch. Does NOT use dp_get_selection(), for performance reasons (this is run on every
**		unselection).
*/
static void update_selection_stats(DirPane *dp)
{
	GList	*iter;
	DirRow	*dr;
	SelInfo	*sel = &dp->dir.sel;

	clear_selection_stats(sel);
	for(iter = GTK_CLIST(dp->list)->selection; iter != NULL; iter = g_list_next(iter))
	{
		dr = gtk_clist_get_row_data(GTK_CLIST(dp->list), GPOINTER_TO_INT(iter->data));
		if(S_ISDIR(DP_ROW_LSTAT(dr).st_mode))
			sel->num_dirs++;
		else
			sel->num_files++;
		sel->num_bytes  += DP_ROW_LSTAT(dr).st_size;
		sel->num_blocks += DP_ROW_LSTAT(dr).st_blocks;
	}
}

/* 1999-04-09 -	Update the tot_XXX fields in <dp>'s directory statistics. Handy after a
**		(Get|Clear)Size.
*/
void dp_update_stats(DirPane *dp)
{
	DirRow	*dr;
	guint	i;

	clear_total_stats(dp);
	clear_selection_stats(&dp->dir.sel);
	for(i = 0; i < dp->dir.num_lines; i++)
	{
		dr = &dp->dir.line[i];

		if(S_ISDIR(DP_ROW_LSTAT(dr).st_mode))
			dp->dir.tot_dirs++;
		else
			dp->dir.tot_files++;
		dp->dir.tot_bytes  += DP_ROW_LSTAT(dr).st_size;
		dp->dir.tot_blocks += DP_ROW_LSTAT(dr).st_blocks;
	}
}

/* 1999-03-30 -	Update information about filesystem for given <dp>. */
static void update_fs_info(DirPane *dp)
{
	int	old_errno = errno;

#if defined(__linux) || defined(__OpenBSD__)
	dp->dir.fs.valid = (statfs(dp->dir.path, &dp->dir.fs.stat) == 0);
#else
	dp->dir.fs.valid = (statvfs(dp->dir.path, &dp->dir.fs.stat) == 0);
#endif

	errno = old_errno;
}

/* 1998-05-17 -	Heia Norge?! Not. Read in a directory, and insert file names into the given
**		directory pane.
** 1998-05-18 -	Added the <root> argument, which must be absolute. The <path> argument will, if
**		it is not also absolute, be added to the <root>, and the result will be read.
** 1998-05-25 -	Moved into dirpane module, and tidied up somewhat.
** 1998-06-03 -	Fixed a bug where files where sometimes selected from the start.
** 1998-06-07 -	Now pokes away any selection info there might be.
** 1998-06-15 -	Did a two-liner (sort of) hack to support environment variables in source path.
**		Very simplistic; does not handle embedded vars.
** 1998-07-13 -	Redesigned the way lines are stored; the name is no longer stored in its full
**		MAXNAMLEN glory, since that wastes *heaps* of memory. Instead, the fut_dir_length()
**		function now is kind enough to also compute the number of name-bytes, and
**		return that. This makes it possible to just allocate one more chunk of memory,
**		and store all names in there, with pointers from the individual lines. This
**		shrank the /dev representation on my system from 421524 bytes (!) down to 93672
**		(1301 entries). Still much, but a lot better.
** 1998-08-25 -	Now uses the fut_path_component() function to parse the path. This buys better
**		handling of environment variable look-ups.
** 1998-09-02 -	Optimized and improved. Now doesn't get all confused if we can't enter a given
**		directory (which easily happens).
** 1999-01-03 -	After some serious bug hunting (cheered on by Alain Valleton), I discover that
**		the realloc() I'm linking against is broken: realloc(some_ptr, 0) doesn't return

** 1999-05-11 -	Contemplated the prototype to this function, and modified it. Now takes a single
**		argument <path>, which is the absolute path we wish to go to. If the path is not
**		absolute, it is taken relative to the current directory of the pane.
*/
void dp_enter_dir(DirPane *dp, const gchar *path)
{
	DIR		*dir;
	struct dirent	*de;
	gchar		new_path[PATH_MAX], tmp[PATH_MAX];
	gint		num;
	guint		num_link = 0;
	gsize		auxsize = 0;

	if(fut_path_component(path, tmp) == 0)		/* Do some substitutions. */
		return;

	if((tmp[0] != '\0') && (tmp[0] != '/'))
		g_snprintf(new_path, sizeof new_path, "%s/%s", dp->dir.path, tmp);
	else
		str_strncpy(new_path, tmp, sizeof new_path);

	mnt_entering(dp->main, new_path, MNT_OFTEN);

	if(!fut_cd(new_path, NULL, 0))
	{
		if(dp->dir.path[0] == '\0' && fut_cd(".", NULL, 0))	/* If no previous, try current. */
			dp_enter_dir(dp, ".");
		else
		{
			err_set(dp->main, errno, "CD", new_path);
			err_show(dp->main);
		}
		return;
	}

	if(!getcwd(new_path, sizeof new_path))
	{
		err_set(dp->main, errno, "getcwd()", new_path);
		return;
	}

	if((num = fut_dir_length(dp, ".", &auxsize, &num_link)) >= 0)
	{
		dph_leaving_dir(dp, dp->dir.path);
		str_strncpy(dp->dir.path, new_path, sizeof dp->dir.path);
		clear_stats(dp);
		update_fs_info(dp);
		if(num == 0)			/* Treat empty directories specially. */
		{
			if(dp->dir.line != NULL)
			{
				g_free(dp->dir.line);
				dp->dir.line = NULL;
			}
			if(dp->dir.auxbuf != NULL)
			{
				g_free(dp->dir.auxbuf);
				dp->dir.auxbuf = NULL;
			}
		}
		else
		{
			dp->dir.line   = g_realloc(dp->dir.line, num * sizeof *dp->dir.line);
			dp->dir.auxbuf = g_realloc(dp->dir.auxbuf, auxsize);
			if((dir = opendir(".")) != NULL)
			{
				struct stat	*auxstat = (struct stat *) dp->dir.auxbuf;
				gchar		*auxstr = dp->dir.auxbuf + (num_link * sizeof *auxstat);
				DirRow		*row;

				typ_identify_begin(dp->main);
				for(row = dp->dir.line; ((de = readdir(dir)) != NULL);)
				{
					if(!(dp->main->cfg.dir_filter(de->d_name) && fut_check_hide(dp, de->d_name)))
						continue;

					if(lstat(de->d_name, &DP_ROW_LSTAT(row)) == 0)
					{
						DP_ROW_NAME(row) = auxstr;
						strcpy(auxstr, de->d_name);
						auxstr += strlen(de->d_name) + 1;
						DP_ROW_SET_STAT(row, &DP_ROW_LSTAT(row));
						DP_ROW_SET_LINKNAME(row, NULL);

						if(S_ISLNK(DP_ROW_LSTAT(row).st_mode))	/* Is it a symlink? */
						{
							gint	len;

							len = readlink(de->d_name, auxstr, PATH_MAX);
							if(len < 0)
								len = 0;
							auxstr[len] = '\0';
							DP_ROW_SET_LINKNAME(row, auxstr);
							auxstr += (len + 1);
							if(stat(de->d_name, auxstat) == 0)
							{
								DP_ROW_SET_STAT(row, (struct stat *) auxstat);
								auxstat++;
							}
						}
						statistics_add_entry(dp, &DP_ROW_LSTAT(row));
						DP_ROW_FLAGS(row) = 0UL;
						if(!S_ISDIR(DP_ROW_LSTAT(row).st_mode))
							DP_ROW_FLAGS_SET(row, DPRF_HAS_SIZE);
						typ_identify(dp->main, row);
						row++;
					}
				}
				typ_identify_end(dp->main, dp->dir.path);
				dp->dir.num_lines = row - dp->dir.line;
				closedir(dir);
			}
		}
		dp_resort(dp);
		dph_entered_dir(dp, dp->dir.path);
	}
	dp_show_stats(dp);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-11-28 -	Rescan and redisplay given pane's contents. Does not lose the selection. */
void dp_rescan_dir(DirPane *dp)
{
	dp_freeze(dp);
	dp_enter_dir(dp, dp->dir.path);
	dp_thaw(dp);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-06-26 -	A lot of code broken out from the giant function below and put here. This
**		makes rebuilding just the list (and not the path entry, for example) a
**		whole lot easier. Useful when config changes...
** 1998-10-26 -	Since the path entry's location now is configurable, this routine is
**		actually never called directly any longer. The true dp_build() always does
**		the job. Function made static.
*/
static void dp_build_list(DPFormat *fmt, DirPane *dp)
{
	GtkWidget	*list;
	gchar		*title[DP_COLUMNS_MAX], name[32];
	guint		i;

	for(i = 0; i < fmt->num_columns; i++)
		title[i] = fmt->format[i].title;
	if((list = gtk_clist_new_with_titles(fmt->num_columns, title)) != NULL)
	{
		g_snprintf(name, sizeof name, "mainPane%d", dp->index);
		gtk_widget_set_name(list, name);
		GTK_WIDGET_UNSET_FLAGS(list, GTK_CAN_FOCUS);
		for(i = 0; i < fmt->num_columns; i++)	/* A rather gross hack. */
		{
			gtk_clist_set_column_width(GTK_CLIST(list), i, fmt->format[i].width);
			gtk_clist_set_column_justification(GTK_CLIST(list), i, fmt->format[i].just);
			GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(list)->column[i].button, GTK_CAN_FOCUS);
		}
		gtk_signal_connect(GTK_OBJECT(list), "button_press_event", GTK_SIGNAL_FUNC(evt_dirpane_press), dp);
		gtk_signal_connect(GTK_OBJECT(list), "click_column", GTK_SIGNAL_FUNC(evt_dirpane_col_click), dp);
		gtk_signal_connect(GTK_OBJECT(list), "select_row", GTK_SIGNAL_FUNC(evt_dirpane_select_row), dp);
		gtk_signal_connect(GTK_OBJECT(list), "unselect_row", GTK_SIGNAL_FUNC(evt_dirpane_unselect_row), dp);
		gtk_signal_connect(GTK_OBJECT(list), "resize_column", GTK_SIGNAL_FUNC(evt_dirpane_resize_column), dp);

		gtk_widget_set_events(list, GDK_POINTER_MOTION_MASK);
		if(fmt->scrollbar_always)
			gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dp->scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
		else
			gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dp->scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
		gtk_clist_set_selection_mode(GTK_CLIST(list), GTK_SELECTION_EXTENDED);
		dp->list = GTK_CLIST(list);
	}
}

/* 1998-05-23 -	Rewrote this one, now encloses stuff in a frame, which provides a fairly nice way to
**		indicate current directory. Now also uses the new dirpane formatting/config stuff.
** 1998-06-26 -	Cut away the code above (dp_build_list), making this function a lot leaner.
** 1998-09-06 -	Frame removed, since I've become cool enough to use styles to just change the
**		background color of the pane's column buttons. Looks great!
** 1998-10-26 -	Now supports configuring the position of the path entry (above or below).
** 1998-11-26 -	The path entry widget is now a combo box, in preparation for history.
**		BUG:	The 'popwin' combo member is poked with, but it shouldn't be. I failed to
**			come with another method of accurately finding out when the combo list
**			disappears after a selection, however. :(
*/
GtkWidget * dp_build(MainInfo *min, DPFormat *fmt, DirPane *dp)
{
	GtkWidget	*hbox, *pr;

	if(pane_selected != NULL)
	{
		gtk_style_unref(pane_selected);
		pane_selected = NULL;
	}
	if(focus_style != NULL)
	{
		gtk_style_unref(focus_style);
		focus_style = NULL;
	}

	if(dp == NULL)
		return NULL;

	if((dp->vbox = gtk_vbox_new(FALSE, 0)) != NULL)
	{
		GtkWidget	*dummy;
		GdkPixmap	*pix;
		GdkBitmap	*msk;

		dp->scwin = gtk_scrolled_window_new(NULL, NULL);
		if(fmt->sbar_pos == SBP_LEFT)
			gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(dp->scwin), GTK_CORNER_TOP_RIGHT);
		else if(fmt->sbar_pos == SBP_RIGHT)
			gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(dp->scwin), GTK_CORNER_TOP_LEFT);
		dp_build_list(fmt, dp);
		gtk_container_add(GTK_CONTAINER(dp->scwin), GTK_WIDGET(dp->list));
		gtk_widget_show(GTK_WIDGET(dp->list));
		hbox = gtk_hbox_new(FALSE, 0);
		pr = gtk_button_new();
		dummy = gtk_window_new(GTK_WINDOW_TOPLEVEL);	/* If you see this, please don't tell anyone. */
		gtk_widget_realize(dummy);
		if((pix = gdk_pixmap_create_from_xpm_d(dummy->window, &msk, NULL, icon_dirparent_xpm)) != NULL)
		{
			GtkWidget	*pmap;

			if((pmap = gtk_pixmap_new(pix, msk)) != NULL)
			{
				gtk_container_add(GTK_CONTAINER(pr), pmap);
				gtk_widget_show(pmap);
			}
		}
		gtk_widget_destroy(dummy);			/* (Hoping the pixmap doesn't share any state...). */
		GTK_WIDGET_UNSET_FLAGS(pr, GTK_CAN_FOCUS);
		gtk_signal_connect(GTK_OBJECT(pr), "clicked", GTK_SIGNAL_FUNC(evt_pt_clicked), (gpointer) dp);
		gtk_box_pack_start(GTK_BOX(hbox), pr, FALSE, FALSE, 0);
		gtk_widget_show(pr);

		dp->path = gtk_combo_new();
		gtk_combo_set_value_in_list(GTK_COMBO(dp->path), FALSE, TRUE);
		gtk_combo_set_use_arrows_always(GTK_COMBO(dp->path), TRUE);
		gtk_combo_disable_activate(GTK_COMBO(dp->path));
		/* The next line is probably illegal (in theory). */
		gtk_signal_connect(GTK_OBJECT(GTK_COMBO(dp->path)->popwin), "hide", GTK_SIGNAL_FUNC(evt_path_history), dp);
		gtk_signal_connect(GTK_OBJECT(GTK_COMBO(dp->path)->entry), "activate", GTK_SIGNAL_FUNC(evt_new_path), dp);
		gtk_signal_connect(GTK_OBJECT(GTK_COMBO(dp->path)->entry), "focus_in_event", GTK_SIGNAL_FUNC(evt_path_focus), dp);
		gtk_signal_connect(GTK_OBJECT(GTK_COMBO(dp->path)->entry), "focus_out_event", GTK_SIGNAL_FUNC(evt_path_unfocus), dp);
		gtk_box_pack_start(GTK_BOX(hbox), dp->path, TRUE, TRUE, 0);
		gtk_widget_show(dp->path);
		dp->hide = gtk_toggle_button_new_with_label("H");
		GTK_WIDGET_UNSET_FLAGS(dp->hide, GTK_CAN_FOCUS);
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dp->hide), fmt->hide_allowed);
		gtk_signal_connect(GTK_OBJECT(dp->hide), "clicked", GTK_SIGNAL_FUNC(evt_hide_clicked), dp);
		gtk_box_pack_start(GTK_BOX(hbox), dp->hide, FALSE, FALSE, 0);
		gtk_widget_show(dp->hide);
		if(fmt->path_above)
		{
			gtk_box_pack_start(GTK_BOX(dp->vbox), hbox, FALSE, FALSE, 0);
			gtk_box_pack_start(GTK_BOX(dp->vbox), GTK_WIDGET(dp->scwin), TRUE, TRUE, 0);
		}
		else
		{
			gtk_box_pack_start(GTK_BOX(dp->vbox), GTK_WIDGET(dp->scwin), TRUE, TRUE, 0);
			gtk_box_pack_start(GTK_BOX(dp->vbox), hbox, FALSE, FALSE, 0);
		}		
		gtk_widget_show(hbox);
		gtk_widget_show(dp->scwin);
		gtk_widget_show(dp->vbox);
		dp->dbclk_row = -1;
		dp->focus_row = -1;
		dp->old_focus_row = -1;
	}
	return dp->vbox;
}
