/* $Id: e2_fileview.c 893 2008-05-20 09:42:51Z tpgww $

Portions copyright (C) 2004-2008 tooar <tooar@gmx.net>.

This file is part of emelFM2.
emelFM2 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

emelFM2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/e2_fileview.c
@brief directory content view functions

This file contains functions related to creation of treeviews to display
directory content, plus related selections and sorting.
*/
/**
\page treeviews filelist treeviews

ToDo - description of how to work with the filelist treeviews

\section cell_rend cell-renderers

ToDo
*/

#include "emelfm2.h"
//#include <unistd.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include "e2_dnd.h"
#include "e2_filelist.h"
#include "e2_context_menu.h"
#include "e2_option.h"
//#include "e2_tree_dialog.h"
#include "e2_dialog.h"
#include "e2_task.h"

//allow in-place renaming of items TOO ANNOYING AND CLUMSY
//(initiated by left-click on filename when line is not selected)
//#define EDIT_INPLACE

//do incremental filelist refreshes
//#define E2_NEWREFRESH

//#ifdef E2_NEWREFRESH
typedef enum
{
	REFRESH_ADD = 0,
	REFRESH_DELETE,	//unused
	REFRESH_CHANGE,
	REFRESH_KEEP,
} E2_RefreshMode;

//one of these for each entry in a new list of FileInfo's created to check
//against current FileInfo's in a view
typedef struct _E2_RefreshInfo
{
	guint newindx;	//index of row in changes liststore
	guint oldindx;	//index of row in current liststore (ex. when adding)
	E2_RefreshMode oldmode; //what to do with that row
} E2_RefreshInfo;
//#endif

typedef struct _E2_DirHistoryEntry
{
	gchar path[PATH_MAX];	//must be at start of stuct (struct ptr sometimes used for lists of strings)
	gint toprow;	//index of top visible row
	gint selrow;	//index of 1st selected row, -1 if no selection
	E2_FSSensitive case_sensitive_names;
} E2_DirHistoryEntry;

extern GtkTargetEntry target_table[];
extern guint n_targets;

//this holds, in fixed column order (used in model & config, cache data),
// the displayed column widths, a row for each pane
gint col_width_store[2][MAX_COLUMNS];
//this holds, in fixed column order (used in model & config, cache data),
// the displayed column order, a row for each pane
gint stored_col_order[2][MAX_COLUMNS];
//this holds column data from the above arrays, for cache reading/writing
//GList *cols_data;
//this holds, in displayed column order
// the fixed column order, a row for each pane
gint displayed_col_order[2][MAX_COLUMNS];

// Colors
//GdkColor LIST_COLOR;
#ifdef E2_SELTXT_RECOLOR
GdkColor selectedtext;
#endif

gboolean btn2_released;	//flag used to decide if middle-btn DnD menu is wanted

//local copy of flag
static gboolean button2updir;
//number of last mouse button press, for distinguishing drags
static gboolean pane1_all = FALSE, pane2_all=FALSE;
//these are related to column dragging
static gint header_button;
static gboolean block;
#ifdef E2_ALTLEFTMOUSE
/*this provides a quick check whether the left btn has been pressed
set and cleared in button-press and -release callbacks, respectively*/
gboolean left_pressed;
/*flag for whether selection-by-dragging is underway
set TRUE in drag-begin cb, cleared in drag-data-get cb*/
gboolean drag_sel_now;
//max msec between clicks when checking for a doubleclick
//extern guint click_interval;
#endif

//static
//gboolean cd_blocked = FALSE;	//TRUE will block re-entrant and parallel cd's
//pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER; //or PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
//GStaticRecMutex list_mutex;
//these data should only be accessed with mutex protection
//E2_CDdata p1cd_data;
//E2_CDdata p2cd_data;

static gpointer _e2_fileview_change_dir (E2_Listman *cddata);

//functions for sorting of treeviews
static gint _e2_fileview_name_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_size_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_perm_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_user_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_group_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_mdate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_adate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);
static gint _e2_fileview_cdate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction);

E2_Column e2_all_columns[MAX_COLUMNS] = {
	{N_("Filename"), 120, _e2_fileview_name_sort},
	{N_("Size"), 80, _e2_fileview_size_sort},
	{N_("Permissions"), 80, _e2_fileview_perm_sort},
	{N_("Owner"), 60, _e2_fileview_user_sort},
	{N_("Group"), 60, _e2_fileview_group_sort},
	{N_("Modified"), 120, _e2_fileview_mdate_sort},
	{N_("Accessed"), 120, _e2_fileview_adate_sort},
	{N_("Changed"), 120, _e2_fileview_cdate_sort} };

/**
@brief item-name sort-order comparison function

This compares 'sort-key' row data, which is utf-8 compatible
If the case-insensitive sorting option was in force when the data
was loaded into the model, the keys are 'case-folded'
ie this is not so dynamic as we might want
Directories are placed before other things.
NB utf8 collate keys for ascii text are case insensitive !!

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_name_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb, *keya, *keyb;
	gint result;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, NAMEKEY, &keya, -1);
	gtk_tree_model_get (model, b, FILENAME, &nameb, NAMEKEY, &keyb, -1);
	if (g_str_equal (namea, "../"))
		result = (order) ? -1 : 1;
	else
		if (g_str_equal (nameb, "../"))
			result = (order) ? 1 : -1;
	else
		if (e2_fs_is_dir_fast (namea))
		{
			if (e2_fs_is_dir_fast (nameb))
				result = strcmp (keya, keyb);
			else
		  		result = (order) ? -1 : 1;
		}
	else
		if (e2_fs_is_dir_fast (nameb))
			result = (order) ? 1 : -1;
	else
		result = strcmp (keya, keyb);

	g_free (namea);
	g_free (nameb);
	g_free (keya);
	g_free (keyb);

	return result;
}
/**
@brief item-extension sort-order comparison function

This compares 'sort-key' row data, which is utf-8 compatible
Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
//FIXME = support case-insensitive sorting
gint e2_fileview_ext_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb, *keya, *keyb, *ea, *eb;
	gint result;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, NAMEKEY, &keya, -1);
	gtk_tree_model_get (model, b, FILENAME, &nameb, NAMEKEY, &keyb, -1);
	if (g_str_equal (namea, "../"))
		result = (order) ? -1 : 1;
	else
		if (g_str_equal (nameb, "../"))
			result = (order) ? 1 : -1;
	else
		if (e2_fs_is_dir_fast (namea))
		{
			if (e2_fs_is_dir_fast (nameb))
				result = strcmp (keya, keyb);
			else
		  		result = (order) ? -1 : 1;
		}
	else
		if (e2_fs_is_dir_fast (nameb))
			result = (order) ? 1 : -1;
	else
	{
		if ((ea = strrchr (namea+1, '.')) == NULL)	//always ascii '.', don't need g_utf8_strrchr()
		{	// row a has NO extension.. check row b
			if ((strrchr (nameb+1, '.')) != NULL)
				result = -1;
			else
				result = strcmp (keya, keyb);
		}
		// row a HAS an extension.. check row b
		else
			if ((eb = strrchr (nameb+1, '.')) == NULL)
				result = 1;
		// both have extensions
		else
		{
			result = g_utf8_collate (ea, eb);
			//if they are the same, sort by name
			if (result == 0)
				result = strcmp (keya, keyb);
		}
	}
	g_free (namea);
	g_free (nameb);
	g_free (keya);
	g_free (keyb);
	return result;
}
/**
@brief item-size sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_size_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_size - infob->statbuf.st_size);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_size - infob->statbuf.st_size);
}
/**
@brief item-permission sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_perm_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_mode - infob->statbuf.st_mode);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_mode - infob->statbuf.st_mode);
}
/**
@brief item-owner sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_user_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_uid - infob->statbuf.st_uid);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_uid - infob->statbuf.st_uid);
}
/**
@brief item-group sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_group_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_gid - infob->statbuf.st_gid);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_gid - infob->statbuf.st_gid);
}
/**
@brief item-acess-date sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_adate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending
	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_atime - infob->statbuf.st_atime);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_atime - infob->statbuf.st_atime);
}
/**
@brief item-modification-date sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_mdate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_mtime - infob->statbuf.st_mtime);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_mtime - infob->statbuf.st_mtime);
}

/**
@brief item-status-change-date sort-order comparison function

Directories are placed before other things.

@param model the data model to be interrogated
@param a pointer to model iter for the first row to be compared
@param b pointer to model iter for the second row to be compared
@param direction pointer to the views's sort direction

@return  integer <0, 0 or >0 according to whether a belongs before, = or after b
*/
static gint _e2_fileview_cdate_sort
	(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GtkSortType *direction)
{
	gchar *namea, *nameb;
	FileInfo *infoa, *infob;
	gboolean order = (*direction == GTK_SORT_ASCENDING);  //TRUE for ascending, FALSE for descending

	gtk_tree_model_get (model, a, FILENAME, &namea, FINFO, &infoa, -1);
	if (g_str_equal (namea, "../"))
	{
		g_free (namea);
		return (order) ? -1 : 1;
	}

	gtk_tree_model_get (model, b, FILENAME, &nameb, FINFO, &infob, -1);
	if (g_str_equal (nameb, "../"))
	{
		g_free (namea);
		g_free (nameb);
		return (order) ? 1 : -1;
	}

	gboolean bf = e2_fs_is_dir_fast (nameb);
	g_free (nameb);

	if (e2_fs_is_dir_fast (namea))
	{
		g_free (namea);
		if (bf)
			return (infoa->statbuf.st_ctime - infob->statbuf.st_ctime);
		else
			return (order) ? -1 : 1;
	}

	g_free (namea);
	if (bf)
		return (order) ? 1 : -1;

	return (infoa->statbuf.st_ctime - infob->statbuf.st_ctime);
}
/**
@brief sort treeview for @a view by column @a colnum

This supports sorting by keypress

@param colnum column-enumerator, FILENAME...CHANGED
@param view data structure for the pane

@return
*/
gboolean e2_fileview_sort_column (gint colnum, ViewInfo *view)
{
	if (colnum < FILENAME || colnum > CHANGED)
		return FALSE;

	GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview));
	GtkTreeViewColumn *col = g_list_nth_data (cols, colnum);
	g_list_free (cols);
	gboolean visible;
	g_object_get (G_OBJECT (col), "visible", &visible, NULL);
	if (!visible)
		return FALSE;

	gint old_sortcol;
	GtkSortType old_order;
	gboolean toggle = TRUE;
	GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store);
	gtk_tree_sortable_get_sort_column_id (sortable, &old_sortcol, &old_order);

	if (colnum == FILENAME)
	{
		//only do normal name-sorting
		if (view->extsort)
		{
			//counter the effects of the normal toggle
			if (old_sortcol == colnum)
				toggle = FALSE;
			view->extsort = FALSE;
			gtk_tree_sortable_set_sort_column_id (sortable,
					GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
			gtk_tree_sortable_set_sort_func (sortable, FILENAME,
					e2_all_columns[FILENAME].sort_func,
					&view->sort_order, NULL);
		}
	}

	if (old_sortcol == colnum)
	{	//column is already sorted
		if (toggle)
			view->sort_order = (old_order == GTK_SORT_ASCENDING) ?
				GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
	}
	else
	{  //this is a new sort column
		//replace sort indicator
		gtk_widget_hide (view->sort_arrows[old_sortcol]);
		gtk_widget_show (view->sort_arrows[colnum]);
		//1st click makes it ascending
		view->sort_order = GTK_SORT_ASCENDING;
//		order = TRUE;
	}
	//set appropriate arrow type
	GtkArrowType arrow = (view->sort_order == GTK_SORT_ASCENDING) ?
		GTK_ARROW_DOWN : GTK_ARROW_UP;

	gtk_arrow_set (GTK_ARROW (view->sort_arrows[colnum]), arrow,
			//different shadow types don't do any good, really ...
//			(view->extsort) ? GTK_SHADOW_ETCHED_IN : GTK_SHADOW_OUT);
			GTK_SHADOW_NONE);
	//remember the column, for cacheing
	view->sort_column = colnum;
	//do the sort
	gtk_tree_sortable_set_sort_column_id (sortable, colnum, view->sort_order);
	return TRUE;
}

  /*****************/
 /*** callbacks ***/
/*****************/

/**
@brief cursor change detection

This records the row to which the cursor is moved

@param treeview the widget where the cursor moved
@param view data structure for the pane

@return FALSE, so related signals are not blocked
*/
static gboolean _e2_fileview_cursor_change_cb (GtkTreeView *treeview, ViewInfo *view)
{
	printd (DEBUG, "callback: cursor changed");
	GtkTreePath *path;
	gtk_tree_view_get_cursor (treeview, &path, NULL);
	if (path != NULL) //path can be invalid when last row of a filelist is deleted
	{
		view->row = *gtk_tree_path_get_indices (path);
		gtk_tree_path_free (path);
	}
	printd (DEBUG, "focus row is %d", view->row);
	return FALSE;
}
/**
@brief column-header button-press detection

This fn records which button was clicked, so that when a "release"
callback occurs (which may be when a drag starts, not an actual
release) we can check whether it is still pressed
This is a workaround for gtk's (2.4 at least) behaviour
It relies on detecting clicks on the undocumented column->button widget

@param header UNUSED the col button header widget which was clicked
@param event pointer to event data struct
@param view UNUSED data structure for the view to which the file list belongs

@return FALSE, so related signals are not blocked
*/
static gboolean _e2_fileview_column_header_buttonpress_cb
	(GtkWidget *header, GdkEventButton *event, ViewInfo *view)
{
	printd (DEBUG, "callback: column header button press");
	header_button = event->button;
	return FALSE;
}
/**
@brief column-header button-release detection

This checks whether a release event is real. In gtk 2.4
at least, such an event may be when a column-drag starts,
not an actual release.
It relies on detecting clicks on the undocumented
column->button widget

@param UNUSED header the col button widget which was clicked
@param event pointed to event data struct
@param view data structure for the view to which the file list belongs

@return TRUE, if this is a 'bogus' release, else FALSE
*/
static gboolean _e2_fileview_column_header_buttonrel_cb
	(GtkWidget *header, GdkEventButton *event, ViewInfo *view)
{
	printd (DEBUG, "callback: column header button release");

	if (view != curr_view)
		e2_pane_activate_other ();

	//check for whether button is really still pressed
	//gtk 2.4 emits a bogus extra release signal when column-drag occurs
	gboolean pressed = FALSE;
	if (!block && event->send_event)
	{
		pressed = TRUE;
		block = TRUE;
	}
	else if (block)
		block = FALSE;  //on the second pass, just unblock
//	else
//		pressed = FALSE;

	if (!pressed)
		header_button = -1;  //signal to "clicked" callback not to do anything
	return pressed;	//stop further action on this button if not released yet
}
/**
@brief column-click callback

This fn updates the view sorting, by applying an ascending sort
to a column that is not sorted, or by toggling the sort direction
of a column that is already sorted.
Sort indicators are updated.
This is called when a column header is clicked, before any
associated "column-change" signal.

@param col the view column which was clicked
@param rt data structure for the pane to which the file list belongs

@return
*/
//FIXME <Control>-click is not always passed through to here
static void _e2_fileview_column_header_clicked_cb
	(GtkTreeViewColumn *col, ViewInfo *view)
{
	if (header_button != -1)
		return;		//the button is still down, this is drag action

	printd (DEBUG, "callback: column header click");

	//get which column was cliicked
	GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview));
	gint clicked_col = g_list_index (cols, (gpointer) col);
	g_list_free (cols);

	//get colum-order array for the pane
	gint *order_array = (view == app.pane1.view) ? displayed_col_order[0] : displayed_col_order[1];
	//get clicked-model-column number
	gint i = order_array[clicked_col];
/*	if (i == 0)
	{ //filename column  DO WE WANT TO MAKE THIS DYNAMIC BUT SLOWER ??
		//get a local copy of the case-sensitive flag
		E2_OptionSet *set = e2_option_get_simple ("namesort-case-sensitive");
		case_sensitive = _e2_option_bool_get (set);
	} */
	//get current-sorted-column details
	gint m;
	GtkSortType old_order;
	GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store);
	gtk_tree_sortable_get_sort_column_id (sortable, &m, &old_order);

	gboolean toggle = TRUE;
	gboolean extsort = FALSE;
	if (i == FILENAME)
	{
		GdkModifierType mask = e2_utils_get_modifiers ();
		if (mask & GDK_CONTROL_MASK)
		{	//do extension-sorting
			extsort = TRUE;
			if (!view->extsort)
			{
				//counter the effects of the normal toggle
				if (m == i)
					toggle = FALSE;
				view->extsort = TRUE;
				gtk_tree_sortable_set_sort_column_id (sortable,
					GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
				gtk_tree_sortable_set_sort_func (sortable, FILENAME,
					(GtkTreeIterCompareFunc) e2_fileview_ext_sort,
					&view->sort_order, NULL);
			}
		}
		else
		{	//do normal name-sorting
			if (view->extsort)
			{
				//counter the effects of the normal toggle
				if (m == i)
					toggle = FALSE;
				view->extsort = FALSE;
				gtk_tree_sortable_set_sort_column_id (sortable,
					GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
				gtk_tree_sortable_set_sort_func (sortable, FILENAME,
					e2_all_columns[FILENAME].sort_func,
					&view->sort_order, NULL);
			}
		}
	}

	if (m == i)
	{	//column is already sorted
		if (toggle)
			view->sort_order = (old_order == GTK_SORT_ASCENDING) ?
				GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
	}
	else
	{  //this is a new sort column
		//replace sort indicator
		gtk_widget_hide (view->sort_arrows[m]);
		gtk_widget_show (view->sort_arrows[i]);
		//1st click makes it ascending
		view->sort_order = GTK_SORT_ASCENDING;
//		order = TRUE;
	}
	//set appropriate arrow type
	GtkArrowType arrow;
	if (extsort)
		//FIXME do a more-intuitive arrow
		arrow = (view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
	else
		arrow = (view->sort_order == GTK_SORT_ASCENDING) ? GTK_ARROW_DOWN : GTK_ARROW_UP;

	gtk_arrow_set (GTK_ARROW (view->sort_arrows[i]), arrow,
			//different shadow types don't do any good, really ...
//			(view->extsort) ? GTK_SHADOW_ETCHED_IN : GTK_SHADOW_OUT);
			GTK_SHADOW_NONE);
/* no use, gtk always returns gchar data to heap space
	//setup stack variable space for speedier name sorts
	if (i == FILENAME)
	{
		gchar namespace [2][E2_UTFNAME_MAX];
		gtk_tree_sortable_set_sort_func (sortable, FILENAME,
			e2_all_columns[FILENAME].sort_func,
            (gpointer *) namespace, NULL);
	} */

	//remember the column, for cacheing
	view->sort_column = i;
	//do the sort
	gtk_tree_sortable_set_sort_column_id (sortable, i, view->sort_order);

	gtk_widget_grab_focus (view->treeview);
	return;
}
/**
@brief column-change callback

This fn refreshes the columns-order array for the pane, by
interating over pane columns glist, grabbing each title
like "4", converting it to corresponding integer.
It is called when a column is dropped to a different position
and when the window is closed
(also, multiple times when the window is re-created - CHECKME why)

@param treeview the widget where the action occurred
@param view data structure for the view

@return
*/
static void _e2_fileview_col_change_cb (GtkTreeView *treeview,
	ViewInfo *view)
{
	printd (DEBUG, "callback: column change");
	gint *order_array = (view == &app.pane1_view) ?
		displayed_col_order[0] : displayed_col_order[1];
	GList *cols = gtk_tree_view_get_columns (treeview);
	GList *tmp;
	const gchar *title;
	gint colnum = 0;
	for (tmp = cols; tmp != NULL; tmp = tmp->next)
	{
		title = gtk_tree_view_column_get_title (tmp->data);
		order_array[colnum++] = atoi (title);
	}
	g_list_free (cols);

	//update order of "slave" pane if appropriate
	gint *slave_array;
	ViewInfo *slave_view;
	if (e2_option_bool_get ("pane2-uses-other")
		&& view == &app.pane1_view)
	{
		slave_array = displayed_col_order[1];
		slave_view = &app.pane2_view;
	}
	else
	if (e2_option_bool_get ("pane1-uses-other")
		&& view == &app.pane2_view)
	{
		slave_array = displayed_col_order[0];
		slave_view = &app.pane1_view;
	}
	else
		return;

	printd (DEBUG, "slave-view column change");
	g_signal_handlers_block_by_func (G_OBJECT (slave_view->treeview),
		_e2_fileview_col_change_cb, slave_view);
	cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (slave_view->treeview));
	for (colnum = 0; colnum < MAX_COLUMNS; colnum++)
	{
		gint current_col = order_array[colnum];
		if (slave_array[colnum] != current_col)
		{
			gint j;
			GtkTreeViewColumn *prior_col, *moved_col;
			if (colnum == 0)
				prior_col = NULL;
			else
				prior_col = g_list_nth_data (cols, colnum-1);
			for (j = colnum+1; j < MAX_COLUMNS; j++)
			{
				if (slave_array[j] == current_col)
				{
					moved_col = g_list_nth_data (cols, j);
					gtk_tree_view_move_column_after (
						GTK_TREE_VIEW (slave_view->treeview),
							moved_col, prior_col);
					//this approach seems too complex, but several simpler tries failed !!
					g_list_free (cols);
					cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (slave_view->treeview));
					j = colnum;
					for (tmp = g_list_nth (cols, j); tmp != NULL; tmp=tmp->next)
					{
						const gchar *title = gtk_tree_view_column_get_title (tmp->data);
						slave_array[j] = atoi(title);
						j++;
					}
					break;
				}
			}
		}
	}
	g_list_free (cols);
	g_signal_handlers_unblock_by_func (G_OBJECT (slave_view->treeview),
		_e2_fileview_col_change_cb, slave_view);
}
/**
@brief row-activated callback
Activation is triggered when <Enter> is pressed or when a double-click happens
This "executes" the clicked row item
view->row will have been set by the button-release callback

@param treeview the widget where the button was pressed
@param path model path to the clicked row
@param col UNUSED clicked view column
@param view UNUSED rt data for the view to be worked on

@return
*/
static void _e2_fileview_row_activated_cb (
		GtkTreeView        *treeview,
		GtkTreePath        *tpath,
		GtkTreeViewColumn  *col,
		ViewInfo           *view)
{
	printd (DEBUG, "callback: _e2_fileview_row_activated");
	GtkTreeIter iter;
	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
    if (gtk_tree_model_get_iter (model, &iter, tpath))
	{
		gchar *localpath;
		FileInfo *info;
		gtk_tree_model_get (model, &iter, FINFO, &info, -1);
		//this may append "..", which will be parsed in the cd process
		localpath = e2_utils_dircat (view, info->filename, TRUE);
#ifdef E2_VFS
		VPATH sdata = { localpath, view->spacedata };
		e2_task_backend_open (&sdata, TRUE);
#else
		e2_task_backend_open (localpath, TRUE);
#endif
		g_free (localpath);
	}
}
/**
@brief mouse button press callback

This makes the clicked view active, if it wasn't already.
A button-1 press when there is nothing else selected logs
the clicked model row, if any, in case it's a drag start.
(For anything other than a drag, the row is logged whem the
cursor moves.) The gtk handler will select the row.
Double-clicks are detected (based on cb sequence) here, to
work around gtk's behaviour when its handler is aborted
after a single click
A button-2 press does an updir, if that option is in force,
or else logs the clicked model row, if any.
A button-3 press sets up the relevant context menu, and may
select the line or unselect everything, depending on options in force

@param treeview the widget where the button was pressed
@param event gdk event data
@param view rt data for the view to be worked on

@return TRUE (stop other handlers) for btn 2 (updir) or btn 3 press, else FALSE (allow other handlers)
*/
static gboolean _e2_fileview_button_press_cb (GtkWidget *treeview,
	GdkEventButton *event, ViewInfo *view)
{
	printd (DEBUG, "callback: mouse button %d press event type %d", event->button, event->type);
	//check that we're in the actual treeview (not its header) by making
	//sure that the window is correct
	GdkWindow *window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (treeview));
	if (window != event->window)
		return FALSE;
	//make the clicked pane active, if it wasn't already
	if (view != curr_view)  // && !panechange_started)
		e2_pane_activate_other ();

	GtkTreePath *path;
	GtkTreeSelection *sel;
#ifdef E2_ALTLEFTMOUSE
//	gboolean oldleft = left_pressed;
	//set flag to assist processing any drag
	left_pressed = (event->button == 1); //&& event->type == GDK_BUTTON_PRESS);
	if (left_pressed
	//we don't handle any 'modified' button-press (so that range-selection works as normal)
	//or a click within the specified double-click interval from the prior click
		&& !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK))
		)
	{
		//event-time for detecting double-clicks
/* because we block returns for selected items, double-clicks have to be detected
	and processed here, too
	instead of click-interval, test now relies on the normal cb sequence for doubles
	(pr, rl, pr, pr, rl) i.e. a press without an intervening release is a double (or triple!)
		static guint32 last_event_time = 0;
		guint32 interval = event->time - last_event_time;
		last_event_time = event->time;
		if (interval >= click_interval)	//time-based check for double-click (the 3rd pr will pass this test too)
		{ */
			/*if we've passed all the tests, and a selected item is clicked,
			we block gtk's deselection of other items until the button is
			released. Then, we'll know how to treat it*/
		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview),
			event->x, event->y, &path, NULL, NULL, NULL))
		{
//				sel = view->selection;
				if (gtk_tree_selection_path_is_selected (view->selection, path))
				{
					//sometimes the focus doesn't get transferred properly by gtk, so ...
					gtk_widget_grab_focus (view->treeview);

//					if (oldleft)	//this is a fake press, generated as part of a double-click
					if (event->type == GDK_2BUTTON_PRESS)
					{
						printd (DEBUG, "intercepted mouse double-click");
						//process it as a double-click
						_e2_fileview_row_activated_cb (GTK_TREE_VIEW (treeview), path, NULL, view);
					}
					gtk_tree_path_free (path);
					//note that returning TRUE blocks some normal treeview behaviour
					//like refocussing a selected item and repeated double-clicks
					//and allowing in-place editing
/*annoying, interferes with double-clicks
#ifdef EDIT_INPLACE
					return (event->type != GDK_BUTTON_PRESS &&
						gtk_tree_selection_count_selected_rows (view->selection) == 1);
#else */
					return TRUE;	//wait until release, to see what to do with the selected row
//#endif
				}
				gtk_tree_path_free (path);
			}
//		}
	}
	else if (left_pressed && event->type != GDK_BUTTON_PRESS)
		return TRUE;	//kill left multi-clicks with mod key(s)
	else
#endif
/*		if (event->button == 1 &&
			(event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS))
			)
		{	//double or triple left-click
		}
	else
*/		if (event->button == 2)
			return TRUE;	//wait until release, to see whether to select the row
	else if (event->button == 3)
	{
		if (e2_option_bool_get ("windows-right-click")
//			&& (view->tagged == NULL)
		   )
		{
			//if windows right click is enabled, we want to also select the item,
			//if not already done, and clear any other selected item(s) unless this
			//one is selected already
			sel = view->selection;
			if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview),
				event->x, event->y, &path, NULL, NULL, NULL))
			{
				if (!gtk_tree_selection_path_is_selected (sel, path))
					gtk_tree_selection_unselect_all (sel);
				gtk_tree_selection_select_path (sel, path);
				//do this now because menu created before cursor-move cb
				view->row = *gtk_tree_path_get_indices (path);
				gtk_tree_path_free (path);
			}
			else if (! e2_option_bool_get ("windows-right-click-extra"))
				gtk_tree_selection_unselect_all (sel);
		}
		//context menu
		gint type = 0;
		guint modifiers = gtk_accelerator_get_default_mod_mask ();
		modifiers &= event->state;
		if (modifiers == GDK_SHIFT_MASK) type = 1;
		else if (modifiers == GDK_CONTROL_MASK) type = 2;
		else if (modifiers == (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) type = 3;
		e2_context_menu_show (event->button, event->time, type);
		return TRUE;
	}
	return FALSE;
}
/**
@brief mouse button release callback

This checks if the click was a left click. If so, and it's not the
end of a drag, or an 'un-select' click, the clicked row is logged.
Detection of the end of a drag relies on an undocumented
check whether the release happened in the same pane as
was clicked.

@param treeview UNUSED where the button was _pressed_
@param event
@param view rt data for the view where the button was _pressed_

@return TRUE (stop other handlers) if updir initiated, else FALSE (allow other handlers)
*/
static gboolean _e2_fileview_button_release_cb (GObject *treeview,
	GdkEventButton *event, ViewInfo *view)
{
	printd (DEBUG, "callback: button release");
	//check that we're in the actual treeview (not its header) by making
	//sure that the window is correct
	//(this test will also fail at the end of a drag to the other pane)
	GdkWindow *window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (treeview));
	if (window != event->window)
		return FALSE;
	//for releases, there is no distinction between 1- 2- or 3- buttons
#ifdef E2_ALTLEFTMOUSE
	if (event->button == 1)
	{
		left_pressed = FALSE;
		//we ignore releases when a modifier is pressed,
		//and normal-drag ends,
		//  i.e. event->send_event seems to be FALSE (ie the event was not sent explicitly)
		//and drag-select ends
		if (!(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK))
			&& event->send_event == 0
			&& !drag_sel_now)
		{	/*there was no drag-selection, or the drag did not cover
			anything but the clicked item
			we want an outcome same as from a normal button-press
			i.e. select just the clicked row and log it, if any */
			GtkTreePath *path;
			if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview),
				event->x, event->y, &path, NULL, NULL, NULL))
			{	//we're releasing over a treeview-item (which must have been selected)
				GtkTreeSelection *sel = view->selection;
				if (gtk_tree_selection_count_selected_rows (sel) > 1)
				{
					gtk_tree_selection_unselect_all (sel);
					gtk_tree_selection_select_path (sel, path);
					//record the row as the last-selected one
//					view->row = *gtk_tree_path_get_indices (path);NOW IN CURSOR MOVE CB
				}
				gtk_tree_path_free (path);
			}
		}
	}
	else
#endif
		if (event->button == 2)
	{
//		printd (DEBUG, "callback: mouse button-2 release");
		//correctly distinguish drag releases, and normal clicks
		//event->send_event seems to be FALSE (ie the event was not sent explicitly)
		//for releases in the same pane as clicked??
		if (event->send_event == 0)
		{	//not a drag release
			if (button2updir)
			{
				printd (DEBUG, "up");
				E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane;
				e2_pane_change_dir (rt, "..");
				return TRUE;
			}
			//otherwise, maybe log the row
			//don't bother with modifers-check in this context
//			guint modifiers = gtk_accelerator_get_default_mod_mask ();
//			if ((event->state & modifiers) != GDK_CONTROL_MASK)
			if (!(event->state & GDK_CONTROL_MASK))
			{	//not an "unselect" click
				GtkTreePath *path;
				if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview),
					event->x, event->y, &path, NULL, NULL, NULL))
				{
					// row was NOT selected when the mouse was clicked
//					GtkTreeSelection *sel = view->selection;
					gtk_tree_selection_select_path (view->selection, path);
					//record the row as the last-selected one
//					view->row = *gtk_tree_path_get_indices (path);NOW IN CURSOR MOVE CB
					gtk_tree_path_free (path);
				}
			}
		}
		else
			//set signal for DnD handler
			btn2_released = TRUE;
	}
	else
		btn2_released = FALSE;

	return FALSE;
}
/**
@brief set popup menu position

This function is supplied when calling gtk_menu_popup(), to position
the displayed menu.
set @a push_in to TRUE for menu completely inside the screen,
FALSE for menu clamped to screen size

@param menu UNUSED the GtkMenu to be positioned
@param x place to store gint representing the menu left
@param y place to store gint representing the menu top
@param push_in place to store pushin flag
@param treeview in focus when the menu key was pressed

@return
*/
void e2_fileview_set_menu_position (GtkMenu *menu,
	gint *x, gint *y, gboolean *push_in, GtkWidget *treeview)
{
	gint left, top;
	gtk_window_get_position (GTK_WINDOW (app.main_window), &left, &top);
	GtkAllocation alloc = treeview->allocation;
	*x = left + alloc.x + alloc.width/2;
	*y = top + alloc.y +alloc.height/2 - 30;
	*push_in = FALSE;
}
/**
@brief menu button callback (does not apply for 'modified' menu button)

Sets up the relevant context menu

@param treeview UNUSED the widget in focus when the action was initiated
@param view UNUSED rt data for the view to be worked on

@return TRUE always
*/
/*static gboolean _e2_fileview_popup_cb (GtkWidget *treeview,
	ViewInfo *view)
{
	gint menu_type = 0;	//default to 'normal' context menu
	//FIXME this check irrelevant, as cb never used for menu btn + Ctrl/Shift
/ *	GdkModifierType state;
	if (gtk_get_current_event_state (&state))
	{
		if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK)) menu_type = 3;
		else if (state & GDK_SHIFT_MASK) menu_type = 1;
		else if (state & GDK_CONTROL_MASK) menu_type = 2;
	} * /
	gint event_time = gtk_get_current_event_time ();
	//button '0' signals this is not a mouse-button click
	e2_context_menu_show (0, event_time, menu_type);
	return TRUE;
} */
/**
@brief treeview key-press callback

@param widget UNUSED the focused treeview widget when the key was pressed
@param event pointer to event data struct
@param view rt data for the view to be worked on

@return TRUE when we handle a 'vi key'
*/
static gboolean _e2_fileview_key_press_cb (GtkWidget *widget,
	GdkEventKey *event, ViewInfo *view)
{
	if (event->keyval == GDK_Menu)
	{
		gint menu_type;
		switch (event->state & gtk_accelerator_get_default_mod_mask ())
		{
			default:
				menu_type = 0;	//'normal' context menu
				break;
			case GDK_SHIFT_MASK:
				menu_type = 1;
				break;
			case GDK_CONTROL_MASK:
				menu_type = 2;
				break;
		//<Alt> is not a valid modifier for mouse right-click, so we also ignore
		//that for the menu-button
			case (GDK_SHIFT_MASK | GDK_CONTROL_MASK):
				menu_type = 3;
				break;
		}
		//button 0 signals this is not a mouse-button click
		e2_context_menu_show (0, event->time, menu_type);
		return TRUE;
	}
	return FALSE;
}
#ifdef EDIT_INPLACE
/**
@brief disable filelist refreshing during a filename edit

@param renderer the renderer for the cell
@param editable the interface to @a renderer
@param path_string string form of gtk tree path to the row to be amended
@param view pointer to view data struct

@return
*/
static void _e2_fileview_edit_start_cb (GtkCellRenderer *renderer,
	GtkCellEditable *editable, gchar *path_string, ViewInfo *view)
{
	printd (DEBUG, "start cell edit cb");
	e2_filelist_disable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE);
	//disable keypressed that will interfere
	g_signal_handlers_block_by_func (G_OBJECT (view->treeview),
		_e2_fileview_key_press_cb, view);
}
/**
@brief enable filelist refreshing after an aborted filename edit

@param renderer the renderer for the cell
@param view pointer to view data struct

@return
*/
static void _e2_fileview_edit_cancel_cb (GtkCellRenderer *renderer,
	ViewInfo *view)
{
	printd (DEBUG, "cancel cell edit cb");
	g_signal_handlers_unblock_by_func (G_OBJECT (view->treeview),
		_e2_fileview_key_press_cb, view);
	e2_filelist_enable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE);
}
/**
@brief save edited text value in the underlying treestore

@param renderer the renderer for the cell
@param path_string string form of gtk tree path to the row to be amended
@param new_text replacement text string for the cell
@param view  pointer to view data struct

@return
*/
static void _e2_fileview_name_edited_cb (GtkCellRendererText *cell,
	gchar *path_string, gchar *new_text, ViewInfo *view)
{
	if (new_text != NULL)	//probably always TRUE
	{
		printd (DEBUG, "filename edited cb, new text is %s", new_text);
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter_from_string (view->model, &iter,
			path_string))
		{
			gchar *oldname;
			gtk_tree_model_get (view->model, &iter, FILENAME, &oldname, -1);
			if (!( g_str_equal (oldname, new_text)
				|| g_str_equal (oldname, G_DIR_SEPARATOR_S)	//some things can't be changed
				|| g_str_equal (oldname, "../")))
			{
				gchar *old, *new, *utfold, *utfnew, *replacement;
				DialogButtons choice;
				gint len;
				gboolean isdir, success;

				len = strlen (oldname);
				isdir = (*(oldname + len - sizeof (gchar)) == G_DIR_SEPARATOR); //ascii check ok
				if (isdir)
					*(oldname + len - sizeof (gchar)) = '\0';	//strip trailer

				utfold = e2_utils_dircat (view, oldname, FALSE);
				old = F_FILENAME_TO_LOCALE (utfold);
				utfnew = e2_utils_dircat (view, new_text, FALSE);
				new = F_FILENAME_TO_LOCALE (utfnew);
				len = strlen (new);

				if (*(new + len - sizeof (gchar)) == G_DIR_SEPARATOR) //ascii check ok
					*(new + len - sizeof (gchar)) = '\0';	//strip trailer

				if (e2_option_bool_get ("confirm-overwrite")
					&& !e2_fs_access2 (new E2_ERR_NONE()))
				{
					e2_filelist_enable_one_refresh ((view == curr_view) ?
						PANEACTIVE : PANEINACTIVE);
					choice = e2_dialog_ow_check (NULL, new, NONE);
					e2_filelist_disable_one_refresh ((view == curr_view) ?
						PANEACTIVE : PANEINACTIVE);
				}
				else
					choice = OK;

				if (choice == OK)
				{
					gdk_threads_leave ();
					//CHECKME what if a task-Q is is progress ?
					success = e2_task_backend_rename (old, new);
					gdk_threads_enter ();
					if (success)
					{
						if (isdir && !g_str_has_suffix (new_text, G_DIR_SEPARATOR_S))
							replacement = g_strconcat (new_text, G_DIR_SEPARATOR_S, NULL);
						else if (!isdir && g_str_has_suffix (new_text, G_DIR_SEPARATOR_S))
						{
							replacement = g_strdup (new_text);
							*(replacement + strlen (replacement) - sizeof (gchar)) = '\0';
						}
						else
							replacement = (gchar *) new_text;

						GtkTreeIter child;
						gtk_tree_model_filter_convert_iter_to_child_iter
							(GTK_TREE_MODEL_FILTER (view->model), &child, &iter);
						gtk_list_store_set (view->store, &child, FILENAME, replacement, -1);

						if (replacement != new_text)
							g_free (replacement);
					}
					//editing works only on unselected rows and anyway,
					//doesn't stay selected after the next refresh
//					gtk_tree_selection_select_iter (view->selection, &iter);
//					if (success)
//						e2_filelist_request_refresh (view->dir, TRUE); CHECKME ok without E2_FAM ?
				}
				F_FREE (old);
				F_FREE (new);
				g_free (utfold);
				g_free (utfnew);
			}
			g_free (oldname);
		}
	}
	g_signal_handlers_unblock_by_func (G_OBJECT (view->treeview),
		_e2_fileview_key_press_cb, view);
	e2_filelist_enable_one_refresh ((view == curr_view) ? PANEACTIVE : PANEINACTIVE);
}
#endif //def EDIT_INPLACE
/* *
@brief timer callback function which checks whether ok to proceed with a cd
@param busy flag to check
@return FALSE when ready to proceed
*/
/*static gboolean _e2_fileview_check_completion (gboolean *busy)
{
	if (*busy)
		return TRUE;
	gtk_main_quit ();
	app.timers[REFRESHWAIT_T] = 0;
	return FALSE;
}
*/
// some filter menu callbacks (others are in filter-dialog files)

void e2_fileview_filter_dirs_cb (GtkWidget *widget, ViewInfo *view)
{  //toggle cached flag to match the menu check button
//	view->filter_directories = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget));
	view->filter_directories = !view->filter_directories;
	e2_fileview_refilter_list (view);
}

void e2_fileview_remove_filters_cb (GtkWidget *widget, ViewInfo *view)
{
	// turn off local flags
	view->name_filter.active = FALSE;
	view->size_filter.active = FALSE;
	view->date_filter.active = FALSE;
	view->filter_directories = FALSE;
	//show the results
	e2_toolbar_toggle_filter_button (view);
	e2_fileview_refilter_list (view);
}
/**
@brief initialise file filter flags to show everything

@param view data structure for view being processed

@return
*/
/*void e2_fileview_initialize_filters (ViewInfo *view)
{
	view->filter_directories = FALSE;

	view->name_filter.active = FALSE;
	g_strlcpy (view->name_filter.pattern, "*",
		      sizeof(view->name_filter.pattern));
	view->name_filter.case_sensitive = TRUE;

	view->size_filter.active = FALSE;
	view->size_filter.size = 0;
	view->size_filter.op = GT;

	view->date_filter.active = FALSE;
	view->date_filter.time = time(NULL);
	view->date_filter.time_type = ATIME;
	view->date_filter.op = GT;
} */
/**
@brief clear filter patterns for view associated with @a view

@param view rt data for the view, assigned when function was initiated

@return
*/
static void _e2_fileview_clear_filter_patterns (ViewInfo *view)
{
	E2_SelectPattern *patterninfo;
	GSList *member;
	if (view->name_filter.compiled_patterns == NULL)
		return;
	for (member = view->name_filter.compiled_patterns; member != NULL; member = member->next)
	{
		patterninfo = (E2_SelectPattern *)member->data;
		g_pattern_spec_free (patterninfo->pspec);
		DEMALLOCATE (E2_SelectPattern, patterninfo);
	}
	g_slist_free (view->name_filter.compiled_patterns);
	view->name_filter.compiled_patterns = NULL;
}
/**
@brief compile filter patterns for view associated with @a view

@param view rt data for the view, assigned when function was initiated

@return TRUE if the process was completed successfully
*/
static gboolean _e2_fileview_compile_filter_patterns (ViewInfo *view)
{
	if (view->name_filter.compiled_patterns != NULL)
		_e2_fileview_clear_filter_patterns (view);

	E2_SelectPattern *patterninfo;
	GSList *members = NULL;
	gchar *s, *p, *freeme;
	gchar save;

	p = view->name_filter.patternptr;
	//maybe several patterns, separated by commas
	while ((s = strchr (p, ',')) != NULL)	//if always ascii ',', don't need g_utf8_strchr()
	{	//check each pattern that is followed by a ','
		while (p[0] == ' ')	//no \t check
			p++;
		if (p == s)
		{	//empty pattern
			p++;
			continue;
		}
		patterninfo = MALLOCATE (E2_SelectPattern);	//too small for slice
		CHECKALLOCATEDWARN (patterninfo, )
		if (patterninfo == NULL)
		{
			g_slist_free (members); //improper cleanup, too bad
			return FALSE;
		}
		members = g_slist_append (members, patterninfo);
		if (p[0] == '!')
		{
			patterninfo->negated = !view->name_filter.invert_mask;
			p++;
		}
		else
		{
			patterninfo->negated = view->name_filter.invert_mask;
			if (p[0] == '\\' && p[1] == '!')
				p++;
		}

		save = *s;
		*s = '\0';

		if (!view->name_filter.case_sensitive)
		{
			freeme = g_utf8_strdown (p, -1);
			patterninfo->pspec = g_pattern_spec_new (freeme);
			g_free (freeme);
		}
		else
			patterninfo->pspec = g_pattern_spec_new (p);

		*s = save;
		NCHR(s);
		p = s;
	}
	//now the last (or only) pattern
	while (p[0] == ' ')
		p++;
	if (p[0] == '\0')
	{
		view->name_filter.compiled_patterns = members;
		return (members != NULL);
	}

	patterninfo = MALLOCATE (E2_SelectPattern); //too small for slice
	CHECKALLOCATEDWARN (patterninfo, )
	if (patterninfo == NULL)
	{
		g_slist_free (members); //improper cleanup, too bad
		return FALSE;
	}
	members = g_slist_append (members, patterninfo);

	if (p[0] == '!')
	{
		patterninfo->negated = !view->name_filter.invert_mask;
		p++;
	}
	else
	{
		patterninfo->negated = view->name_filter.invert_mask;
		if (p[0] == '\\' && p[1] == '!')
			p++;
	}
	if (!view->name_filter.case_sensitive)
	{
		freeme = g_utf8_strdown (p, -1);
		patterninfo->pspec = g_pattern_spec_new (freeme);
		g_free (freeme);
	}
	else
		patterninfo->pspec = g_pattern_spec_new (p);

	view->name_filter.compiled_patterns = members;
	return TRUE;
}
static gchar *name_pattern = NULL;
static gboolean hide_updir = FALSE;	//for speed, set in refilter-list func
/**
@brief treeview row visibility function

@param model the child model of the GtkTreeModelFilter
@param iter pointer to a GtkTreeIter for the row in @a model whose visibility is to be determined
@param view rt data for the view, assigned when function was initiated

@return TRUE if the row is visible
*/
static gboolean _e2_fileview_filter_check (GtkTreeModel *model,
	GtkTreeIter *iter, ViewInfo *view)
{
	FileInfo *info;
	gtk_tree_model_get (model, iter, FINFO, &info, -1);
	gchar *name = info->filename;
	if (ITEM_ISHIDDEN (name)
		&& (
			//name[1] == '\0' || filtered out when dir read
			   (!view->show_hidden && name[1] != '.')
//			|| (name[1] == '.' && name[2] == '\0' && (!e2_option_bool_get ("show-updir-entry") //FIXME faster
			|| (name[1] == '.' && name[2] == '\0' && (hide_updir
			|| (view->dir[0] == G_DIR_SEPARATOR && view->dir[1] == '\0')) //never show updir in root directory
			   )
			)
		)
#ifdef E2_VFSTMP
	//FIXME show updir in root of archive etc v-dirs
#endif
			return FALSE;

	if (!view->filter_directories && e2_fs_is_dir (info, view))
		return TRUE;

	if (view->name_filter.active)
	{
#if 0
		gchar *s, *p, *utf, *freeme;
		gchar save;
		gboolean negated, matched, positive_check = FALSE, result = FALSE;

		p = name_pattern;
		utf = F_FILENAME_FROM_LOCALE (info->filename);	//not DISPLAYNAME
		//maybe several patterns, separated by commas
		while ((s = strchr (p, ',')) != NULL)	//if always ascii ',', don't need g_utf8_strchr()
		{	//check each pattern that is followed by a ','
			save = *s;
			*s = '\0';
			while (p[0] == ' ')
				p++;
			if (p[0] == '!')
			{
				negated = !view->name_filter.invert_mask;
				p++;
			}
			else
			{
				negated = view->name_filter.invert_mask;
				if (p[0] == '\\' && p[1] == '!')
					p++;
			}
			if (!positive_check)
				positive_check = !negated;

			if (!view->name_filter.case_sensitive)
			{
			  freeme = g_utf8_strdown (utf, -1);
			  matched = g_pattern_match_simple (p, freeme);
			  g_free (freeme);
			}
			else
			  matched = g_pattern_match_simple (p, utf);

			*s = save;
			if (matched && negated)
			{
			  F_FREE (utf);
			  return FALSE;
			}
			if (matched && !negated)
			  result = TRUE;	//but keep looking for any later exclude
			//if neither negated nor matched, we don't change result

			NCHR(s);	//in case the matched char is bigger than 1 byte
			p = s;
		}
		//check the last (or only) pattern
		while (p[0] == ' ')
			p++;
		if (p[0] != '\0')
		{
			if (p[0] == '!')
			{
				negated = !view->name_filter.invert_mask;
				p++;
			}
			else
			{
				negated = view->name_filter.invert_mask;
				if (p[0] == '\\' && p[1] == '!')
					p++;
			}
			if (!positive_check)
				positive_check = !negated;

			if (!view->name_filter.case_sensitive)
			{
				freeme = g_utf8_strdown (utf, -1);
				matched = g_pattern_match_simple (p, freeme);
				g_free (freeme);
			}
			else
				matched = g_pattern_match_simple (p, utf);
			if (matched)
				result = !negated;
			//extra check for unmatched final check
			else if (negated && !positive_check)
				result = TRUE;
		}
		F_FREE (utf);
		if (!result)
			return FALSE;
#else	//ndef 0
		//hidden updir's are already refused, above
		if (name[0] != '.' || name[1] != '.' || name[2] != '\0')
		{
			GSList *member;
			E2_SelectPattern *patterninfo;
			gboolean negated = FALSE, matched = FALSE, positive_check = FALSE, result = FALSE;
			gchar *freeme, *utf = F_FILENAME_FROM_LOCALE (info->filename);	//not DISPLAYNAME

			for (member = view->name_filter.compiled_patterns; member != NULL;
				member= member->next)
			{
				patterninfo = (E2_SelectPattern *)member->data;
				negated = patterninfo->negated;
				if (!positive_check)
					positive_check = !negated;

				if (!view->name_filter.case_sensitive)
				{
				  freeme = g_utf8_strdown (utf, -1);
				  matched = g_pattern_match_string (patterninfo->pspec, freeme);
				  g_free (freeme);
				}
				else
				  matched = g_pattern_match_string (patterninfo->pspec, utf);

				if (matched && negated)
				{
				  F_FREE (utf);
				  return FALSE;
				}
				if (matched && !negated)
				  result = TRUE;	//but keep looking for any later exclude
				//if neither negated nor matched, we don't change result
			}
			F_FREE (utf);
			//extra check for unmatched final check
			if (!matched && negated && !positive_check)
				result = TRUE;
			if (!result)
				return FALSE;
		}
#endif //def 0
	}

	if (view->size_filter.active)
	{
		switch (view->size_filter.op)
		{
			case LT:
			  if (info->statbuf.st_size >= view->size_filter.size)
				  return FALSE;
			  break;
			case EQ:
			  if (info->statbuf.st_size != view->size_filter.size)
				  return FALSE;
			  break;
			default:
//			case GT:
			  if (info->statbuf.st_size <= view->size_filter.size)
				  return FALSE;
			  break;
		}
	}

	if (view->date_filter.active)
	{
		switch (view->date_filter.time_type)
		{
			case ATIME:
			  if (view->date_filter.op == GT)
			  {
				if (difftime (view->date_filter.time, info->statbuf.st_atime) > 0)
					return FALSE;
			  }
			  else
				if (difftime (view->date_filter.time, info->statbuf.st_atime) <= 0)
					return FALSE;
			  break;
			case CTIME:
			  if (view->date_filter.op == GT)
			  {
				if (difftime (view->date_filter.time, info->statbuf.st_ctime) > 0)
					return FALSE;
			  }
			  else
				if (difftime (view->date_filter.time, info->statbuf.st_ctime) <= 0)
					return FALSE;
			  break;
			default:
//			case MTIME:
			  if (view->date_filter.op == GT)
			  {
				if (difftime (view->date_filter.time, info->statbuf.st_mtime) > 0)
					return FALSE;
			  }
			  else
				if (difftime (view->date_filter.time, info->statbuf.st_mtime) <= 0)
					return FALSE;
			  break;
		}
	}
	return TRUE;
}
/**
@brief treeview row visibility function for unfiltered view
Assumes "." entry is filtered out when dir data is read
@param model the child model of the GtkTreeModelFilter
@param iter pointer to a GtkTreeIter for the row in @a model whose visibility is to be determined
@param viewdir ptr to view->dir

@return TRUE unless the row is ".." in the root directory
*/
static gboolean _e2_fileview_filter_none (GtkTreeModel *model,
	GtkTreeIter *iter, gchar *viewdir)
{
#ifdef E2_VFSTMP
	//FIXME show updir from root of archive etc v-dirs
#endif
	//do fastest checks in order, to filter updir item from root dir
	if (viewdir[1] == '\0' && viewdir[0] == G_DIR_SEPARATOR)	//ascii ok
	{
		FileInfo *info;
		gtk_tree_model_get (model, iter, FINFO, &info, -1);
		if (info->filename[2] == '\0' && info->filename[1] == '.'
			&& ITEM_ISHIDDEN(info->filename))
			return FALSE;
	}
	return TRUE;
}
/**
@brief update filters for treeview
This changes model, and attaches view to new model
Assumes view attached to a model (store ?)
When new store is created, view->filtered_before will be false
@param view rt data for the view

@return
*/
void e2_fileview_refilter_list (ViewInfo *view)
{
	hide_updir = !e2_option_bool_get ("show-updir-entry");	//save, for speedier filtering
	gboolean filtered_now = (
		 view->name_filter.active ||
		 view->size_filter.active ||
		 view->date_filter.active ||
		!view->show_hidden ||
		 hide_updir );

	if (filtered_now)
	{
		//setup for filter func
		if (view->name_filter.active)
		{
			_e2_fileview_compile_filter_patterns (view);
			if (!view->name_filter.case_sensitive)
				name_pattern = g_utf8_strdown (view->name_filter.patternptr, -1);
			else
				name_pattern = g_strdup (view->name_filter.patternptr);
		}

		if (!view->filtered_before)
		{
			//gtk (2.6 at least) won't allow a changed visibility function,
			//so have to replace the filtermodel to make such change
			//this combination of changes has been confirmed to 0-ref old
			//filtermodel, and leave all other relevant refcounts = 1
			view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (view->store), NULL);
			gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model);
			g_object_unref (G_OBJECT (view->model));

			gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (view->model),
				(GtkTreeModelFilterVisibleFunc) _e2_fileview_filter_check, view, NULL);
		}

		gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (view->model));

		if (view->name_filter.active)
		{
			_e2_fileview_clear_filter_patterns (view);
			g_free (name_pattern);
		}
	}
	else //if (view->filtered_before)
	{	//de-filter or apply unfiltered visibility func
		view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (view->store), NULL);
		gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model);
		g_object_unref (G_OBJECT (view->model));
#ifdef E2_VFSTMP
		//FIXME for v-dir, display ".." in root of archive etc, needs view ptr
#else
		gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (view->model),
			(GtkTreeModelFilterVisibleFunc) _e2_fileview_filter_none, view->dir, NULL);
#endif
		gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (view->model));
	}

	if (view->row > 0)
	{
		guint visible_rows = gtk_tree_model_iter_n_children (view->model, NULL);
		if (view->row > visible_rows - 1)
			view->row = visible_rows - 1;
		//if there was no selection, or the former selection is gone (after filter or deletion)
		//we want relative cursor-moves to stay in the same vicinity
	 	if (gtk_tree_selection_count_selected_rows (view->selection) == 0)
		{
			GtkTreePath *tpath = gtk_tree_path_new_from_indices (view->row, -1);
			//this "marks" the item, but mere selection is not enough for this to work
			gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview), tpath, NULL, FALSE);
			gtk_tree_selection_unselect_path (view->selection, tpath);
			gtk_tree_path_free (tpath);
		}
	}

	view->filtered_before = filtered_now;
}
/** @brief decide whether type-ahead search matches a row

@param model the treemodel being searched
@param column UNUSED the search column (= FILENAME)
@param key the aggregate string of keys sofar pressed
@param iter an iter pointing the row of @a model that should be compared with @a key
@param view pointer to data struct for the view

@return FALSE when a match is found
*/
static gboolean _e2_fileview_match_filename (GtkTreeModel *model, gint column,
	const gchar *key, GtkTreeIter *iter, ViewInfo *view)
{
	gchar *itemname;
	gtk_tree_model_get (model, iter, FILENAME, &itemname, -1);
	gboolean match = g_str_has_prefix (itemname, key);
	if (match)
	{	//we have a match
		GtkTreePath *path = gtk_tree_model_get_path (model, iter);
		view->row = *gtk_tree_path_get_indices (path);
		if (view->row > 0)
			view->row++;	//CHECKME why is this needed ..
		//these selection-flags probably ineffective
		e2_fileview_focus_row (view, view->row, FALSE, FALSE, FALSE, TRUE);
		gtk_tree_path_free (path);
	}
	g_free (itemname);
	return !match;
}
/* *
@brief UNUSED find filename begining with specified char, in current view

@param ch character at the start of the name

@return the row number where the matching name was found, or -1 if not found
*/
/*static gint e2_fileview_find_filename_begining_with (gchar ch)
{
	gint i;
	//number of rows in the model
	gint n = gtk_tree_model_iter_n_children (curr_view->model, NULL);
	GtkTreeIter iter;
	GtkTreeModel *model = curr_view->model;
	gchar *name;
	// search from after the current row to the end of the store
	for (i = curr_view->row+1; i < n; i++)
	{
		if (! gtk_tree_model_iter_nth_child
			(model, &iter, NULL, i))
				continue;  //the specified row doesn't exist any more
		gtk_tree_model_get (model, &iter, FILENAME, &name, -1);

		if (name[0] == ch)
		{
			g_free (name);
			return i;
		}
		else
			g_free (name);
	}
	// search from the first row to the current row
	for (i = 0; i < curr_view->row; i++)
	{
		if (! gtk_tree_model_iter_nth_child
			(model, &iter, NULL, i))
				continue;  //the specified row doesn't exist any more
		gtk_tree_model_get (model, &iter, FILENAME, &name, -1);

		if (name[0] == ch)
		{
			g_free (name);
			return i;
		}
		else
			g_free (name);
	}
	return -1;
}
*/

// Public functions

/**
@brief display and optionally select a numbered row
The treeview is scrolled, and the cursor is moved, whether or not
the focused row is selected
Of course, the focused row may be selected by gtk, independently of @a select_row,
and current selection will probably be cleared by gtk, independently of @a clear_selection
This assumes that BGL is closed
@param view rt data for ther view to be worked on
@param row treeview index of the row that is to be focused
@param select_row TRUE if the focused row needs to be selected
@param clear_selection TRUE if whole selection for the view is to be un-selected first
@param center TRUE to place the focused row near the center of the view window
@param grab_focus TRUE if the treeview is to be given the focus

@return
*/
void e2_fileview_focus_row (ViewInfo *view,
		      gint row,
		      gboolean select_row,
		      gboolean clear_selection,
		      gboolean center,
		      gboolean grab_focus)
{
	if (view != NULL)
	{
		GtkTreeIter iter;
		GtkTreeModel *model = view->model;
			//gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview));
		if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, row))
			return;  //the specified row doesn't exist

		GtkTreeView *tvw = GTK_TREE_VIEW (view->treeview);
		GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
		gtk_tree_view_scroll_to_cell (tvw, path, NULL, center, 0.382, 0.0);
		//remember existing selection, if any, because moving the cursor kills
		//the selection (if that hasn't been done by gtk already)
		GList *selitems = (clear_selection) ? NULL :
	      gtk_tree_selection_get_selected_rows (view->selection, NULL);

		gtk_tree_view_set_cursor (tvw, path, NULL, FALSE); //selects just this row

		if (selitems != NULL)
		{
			GList *member;
			GtkTreePath *tp;
			for (member = selitems; member != NULL; member = member->next)
			{
				tp = (GtkTreePath *) member->data;
				gtk_tree_selection_select_path (view->selection, tp);
				gtk_tree_path_free (tp);
			}
			g_list_free (selitems);
		}

		if (!select_row)
			gtk_tree_selection_unselect_path (view->selection, path);

		if (grab_focus)
			gtk_widget_grab_focus (view->treeview);

		gtk_tree_path_free (path);
	}
}
/* *
@brief UNUSED select 1st row, in specified view, where the filename matches a specified name

model content update is suspended while this is performed

@param view rt data for ther view to be worked on
@param filename string with name of file to be used for selection

@return
*/
/*void e2_fileview_select_row_by_filename (ViewInfo *view, gchar *filename)
{
	gint i;
	//number of rows in the model
	gint n = gtk_tree_model_iter_n_children (curr_view->model, NULL);
	GtkTreeIter iter;
	GtkTreeModel *model = curr_view->model;
	gchar *thisname;
	e2_filelist_disable_refresh();
	// search from after the current row to the end of the store
	for (i = 0; i < n; i++)
	{
		if (! gtk_tree_model_iter_nth_child
			(model, &iter, NULL, i))
				continue;  //the specified row doesn't exist any more
		gtk_tree_model_get (model, &iter, FILENAME, &thisname, -1);

		if (g_str_equal (thisname, filename))
		{
			e2_fileview_focus_row (view, i, TRUE, FALSE, TRUE, TRUE);
			g_free (thisname);
			break;
		}
		else
			g_free (thisname);
	}
	e2_filelist_enable_refresh();
} */
/**
@brief get file info for 1st selected row
This includes a special-case mechanism for getting "..", which may be opened
or activated when updir entries are shown in filelists
@param view data stuct for the view to be worked on
@param updir TRUE to include "../" in the candidates

@return FileInfo data for the 1st selected row, or if none, then NULL
*/
FileInfo *e2_fileview_get_selected_first_local (ViewInfo *view, gboolean updir)
{
	FileInfo *info = NULL;
//	GtkTreeSelection *sel = view->selection;
	GtkTreeModel *model;
	GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model);
	GList *rowpaths;
	GtkTreeIter iter;
	for (rowpaths = base; rowpaths != NULL; rowpaths = rowpaths->next)
	{
		GtkTreePath *first_sel = (GtkTreePath *) rowpaths->data;
		if (gtk_tree_model_get_iter (model, &iter, first_sel))
		{
			gtk_tree_model_get (model, &iter, FINFO, &info, -1);
			if (updir || !g_str_equal (info->filename, ".."))
				break;
			//ignore selection of "../"
			info = NULL;	//in case there is nothing else selected
		}
	}
	g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL);
	g_list_free (base);

	return info;
}
/**
@brief get list of selected rows' info items

This is usable only when the data is used before any chance
of filelist refresh, which would invalidate the listed data pointers

@param view rt data for the view to be worked on

@return list of selected rows' info items, excluding ".."
*/
GList *e2_fileview_get_selected_local (ViewInfo *view)
{
//	g_signal_emit_by_name (G_OBJECT (view->treeview), "end-selection");

//	if (view->tagged != NULL)
//		return g_list_copy (view->tagged);

	GList *selectedrow_data = NULL;
	FileInfo *info;
	GtkTreePath *path;
	GtkTreeIter iter;
//	GtkTreeSelection *sel = view->selection;
	GtkTreeModel *model;
	GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model);
	GList *rowpaths;

	for (rowpaths=base; rowpaths!=NULL; rowpaths=rowpaths->next)
	{
		path = (GtkTreePath *) rowpaths->data;
		if (gtk_tree_model_get_iter (model, &iter, path))
		{
			gtk_tree_model_get (model, &iter, FINFO, &info, -1);
			if (!g_str_equal (info->filename, ".."))	//CHECKME ok, even with localised string?
				selectedrow_data = g_list_append (selectedrow_data, info);
		}
	}
	g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL);
	g_list_free (base);
	return selectedrow_data;
}
/**
@brief  toggle selection of all items in a pane

This function selects or unselects all** items in a pane, depending on
the current state of the corresponding flag, pane1_all or pane2_all
 ** except '..' entries, (if displayed)
The pane will be activated, if it wasn't already.
The function handles clicks of the select-all toggle buttons

@param blah unused widget ptr (in case this is a callback)
@param view fileview for pane that is to be (un)selected

@return
*/
void e2_fileview_select_all (GtkWidget *blah, ViewInfo *view)
{
	if (curr_view != view)
		e2_pane_activate_other ();
	GtkTreeSelection *sel = curr_view->selection;
	gboolean *allnow = (view == app.pane1.view) ? &pane1_all : &pane2_all ;
	if (*allnow)
	{
		gtk_tree_selection_unselect_all (sel);
		*allnow = FALSE;
	}
	else //select
	{
		gtk_tree_selection_select_all (sel);
		*allnow = TRUE;
	}
}
/**
@brief cleanup content of selected file info struct

foreach function for e2_fileview_clean_selected()

@param seldata ptr to the struct to be processed
@param user_data user data passed to g_ptr_array_foreach() UNUSED

@return
*/
void e2_fileview_selected1_clean (E2_SelectedItemInfo *seldata,
	gpointer user_data)
{
/*	g_free (seldata->filename);
#ifdef E2_INCLIST
	gtk_tree_row_reference_free (seldata->ref);
#else
	gtk_tree_path_free (seldata->path);
#endif */
	DEALLOCATE (E2_SelectedItemInfo, seldata);
}
/**
@brief cleanup pointer array of selected file info structs


@param selected ptr to the array to be freed

@return
*/
void e2_fileview_clean_selected (GPtrArray *selected)
{
	g_ptr_array_foreach (selected, (GFunc) e2_fileview_selected1_clean, NULL);
	g_ptr_array_free (selected, TRUE);
}
/**
@brief create pointer array of selected file info structs

This fn creates a pointer array, with pointers to structs containing
selected items' names and other info.
If found, ".." is filtered out.
If found, any trailing / is removed.
Item name strings are localised
Data is duplicated, so the selection is refresh-proof.
Treerow refs support incremental store changes as the selection is processed
The returned array needs to be freed with its data.

@param view is data structure for the view to be interrogated

@return pointer to GPtrArray structure, or NULL if nothing selected
*/
GPtrArray *e2_fileview_get_selected (ViewInfo *view)
{
//	if (view == curr_view)
//		g_signal_emit_by_name (G_OBJECT(view->treeview), "end-selection");
	GtkTreeSelection *sel = view->selection;
	gint count = gtk_tree_selection_count_selected_rows (sel);
	if (count > 0)
	{
		GPtrArray *array = g_ptr_array_sized_new (count);
		GtkTreeModel *model;
		GList *base = gtk_tree_selection_get_selected_rows (sel, &model);
		GList *paths;
		GtkTreeIter iter;
//		gchar *filename, *localfilename;
		FileInfo *info;
		E2_SelectedItemInfo *seldata;
		for (paths = base; paths != NULL; paths = paths->next)
		{
			if (!gtk_tree_model_get_iter (model, &iter, paths->data))
				continue;  //the row has been altered since it was selected
			gtk_tree_model_get (model, &iter, FINFO, &info, -1);
//			gtk_tree_model_get (model, &iter, FILENAME, &filename, -1);
			if (!g_str_equal (info->filename, ".."))
			{
				seldata = ALLOCATE (E2_SelectedItemInfo);
				CHECKALLOCATEDWARN (seldata, continue);
				memcpy (&seldata->filename, &info->filename, sizeof (seldata->filename));
/*				//always replicate filename
//				localfilename = g_strdup (filename);

				//strip any trailer (can't have it on links, at least)
//				gchar *s = localfilename + strlen (localfilename) - 1;
//				if (*s == G_DIR_SEPARATOR)
//					*s = '\0';
//				seldata->filename = localfilename;  //this is utf8
//				seldata->statbuf = info->statbuf; NO LONGER USED
//				seldata->filename = g_strdup (info->filename);

#ifdef E2_INCLIST
				//get a reference in case the model changes during the operation on the selection
				seldata->ref = gtk_tree_row_reference_new (model, paths->data);
#else
				//this is only relevant if store data not changed on the fly
				seldata->path = gtk_tree_path_copy (paths->data);
#endif */
				g_ptr_array_add (array, seldata);
			}
// always free it now			else
//			g_free (filename);
		}
		g_list_foreach (base, (GFunc) gtk_tree_path_free, NULL);
		g_list_free (base);
		if (array->len == 0)	//there was just a ".." selected
		{
			g_ptr_array_free (array, TRUE);
			array = NULL;
		}
		return array;
	}
	return NULL;
}
/**
@brief cleanup one history item

foreach function for e2_fileview_clean_history()

@param seldata ptr to the struct to be processed
@param user_data user data passed to g_ptr_array_foreach() UNUSED

@return
*/
static void _e2_fileview_clean1_history (E2_DirHistoryEntry *data, gpointer user_data)
{
	DEALLOCATE (E2_DirHistoryEntry, data);
}
/**
@brief cleanup history data

@param store for pointer to the list of history items to clean

@return
*/
void e2_fileview_clean_history (GList **history)
{
	if (*history != NULL)
	{
		g_list_foreach (*history, (GFunc) _e2_fileview_clean1_history, NULL);
		g_list_free (*history);
		*history = NULL;
	}
}
/**
@brief get itemname for a specified view and row


@param view is data structure for the view to be interrogated
@param row number of the row to be interrogated REAL or FILTERED ?

@return pointer to duplicated string, or NULL if there's nothing found
*/
gchar *e2_fileview_get_row_name (ViewInfo *view, gint row)
{
	GtkTreeIter iter;
//	GtkTreeModel *model = GTK_TREE_MODEL (view->store); maybe filter
	GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview));

	if (! gtk_tree_model_iter_nth_child (model, &iter, NULL, row))
		return NULL;
	gchar *name;
	gtk_tree_model_get (model, &iter, FILENAME, &name, -1);
	return name;
}
/**
@brief helper to set file-list font
@param view pointer to data struct for the view to be changed
@param fontstr name of font to apply to the filelist associated with @a view

@return
*/
static void _e2_fileview_set_font (ViewInfo *view, gchar *fontstr)
{
	GList* columns, *renderers, *base1, *base2;
	columns = base1 = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview));
	for (; columns != NULL ; columns = columns->next)
	{
		GtkTreeViewColumn *column = columns->data;
		renderers = base2 = gtk_tree_view_column_get_cell_renderers (column);
		for (; renderers != NULL ; renderers = renderers->next)
			g_object_set (renderers->data, "font", fontstr, NULL);
		g_list_free (base2);
	}
	g_list_free (base1);
}
/**
@brief set file-lists font
This is needed to change font after a config dialog that does
not rebuild the whole window
@return
*/
void e2_fileview_set_font (void)
{
	gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ?
		e2_option_str_get ("list-font") : NULL;	//NULL forces default
	_e2_fileview_set_font (curr_view, fontstr);
	_e2_fileview_set_font (other_view, fontstr);
}
/**
@brief set background color of treeview line


@param view data struct for the view to process
@param iter pointer to iter for the row to be changed
@param color pointer to struct with data for the new color

@return
*/
void e2_fileview_set_row_background (ViewInfo *view, GtkTreeIter *iter,
	GdkColor *color)
{
	//set the attribute column for this row
	gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, color, -1);
	view->lit = TRUE;
}
/**
@brief revert background color of treeview line


@param view data struct for the view to process
@param iter pointer to iter for the row to be changed

@return
*/
void e2_fileview_clear_row_background (ViewInfo *view, GtkTreeIter *iter)
{
	//restore the background-attribute column for this row
#ifdef E2_ASSISTED
	GdkColor *bc;
	if (e2_option_bool_get ("color-background-set"))
		bc = e2_option_color_get ("color-background");
	else
		bc = NULL;
	gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, bc, -1);
#else
	gtk_list_store_set (GTK_LIST_STORE (view->store), iter, BACKCOLOR, NULL, -1);
#endif
	view->lit = FALSE;
}
/**
@brief translate array into another form sorted in index order

This is needed to convert the 'fixed' column order used in data models
and config/cache data, to variable order in accord with the displayed view columns
and vice versa

@param src_array pointer to array of ints that is to be translated
@param dest_array pointer to array of ints that stores the translated order
@param size the number of elements to be translated

@return
*/
void e2_fileview_translate_cols_array (gint *src_array, gint *dest_array,
	gint size)
{
	gint i;
	for (i = 0; i < size; i++)
		dest_array[src_array[i]] = i;
}
/**
@brief update columns data, that is not automatically updated, for caching

Columns order and widths are udated and stored in a list
List format is: pane1 {order, width, ...}, pane2 {order, width, ...}

@return
*/
void e2_fileview_update_col_cachedata (void)
{
	//get current column widths
	GtkTreeViewColumn *col, *last;
	G_CONST_RETURN gchar *title;
	gint i, lc;
	GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (app.pane1.view->treeview));
	GList *base = columns;
	lc = 0;
	last = (GtkTreeViewColumn *)columns->data;
	for (; columns != NULL; columns=columns->next)
	{
		col = columns->data;
		title = gtk_tree_view_column_get_title (col);
		//title is "0" ... "7", for cols 0 ... 7
		i = atoi (title);
		col_width_store [0][i] = gtk_tree_view_column_get_width (col);
		//remember this one if it's a candidate for last-displayed column
		if (col_width_store [0][i] > 0)
		{
			lc = i;
			last = col;
		}
	}
	//for last-displayed column, cache the user-specified width, not the
	//expanded-to-fill width
	col_width_store [0][lc] = gtk_tree_view_column_get_fixed_width (last);
	g_list_free (base);

	columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (app.pane2.view->treeview));
	base = columns;
	lc = 0;
	last = (GtkTreeViewColumn *)columns->data;
	for (; columns != NULL; columns=columns->next)
	{
		col = columns->data;
		title = gtk_tree_view_column_get_title (col);
		i = atoi (title);
		col_width_store [1][i] = gtk_tree_view_column_get_width (col);
		//remember this one if it's a candidate for last-displayed column
		if (col_width_store [1][i] > 0)
		{
			lc = i;
			last = col;
		}
	}
	col_width_store [1][lc] = gtk_tree_view_column_get_fixed_width (last);
	g_list_free (base);
	//get columns displayed order
	e2_fileview_translate_cols_array (displayed_col_order[0], stored_col_order[0], MAX_COLUMNS);
	e2_fileview_translate_cols_array (displayed_col_order[1], stored_col_order[1], MAX_COLUMNS);

/*SUPERSEDED BY ARRAY CACHEING
	//setup for list-cache writing
	//start fresh list
	e2_list_free_with_data (&cols_data);
	gchar *buffer;
	gint j;
	for (j = 0; j < 2; j++)
	{ for (i = 0; i < MAX_COLUMNS; i++) {
		//data need to be strings, for the cache writer
		buffer = g_strdup_printf ("%d", stored_col_order[j][i]);
		cols_data = g_list_append (cols_data, buffer);
		buffer = g_strdup_printf ("%d", col_width_store[j][i]);
		cols_data = g_list_append (cols_data, buffer);
	}}
*/
}
/* *
@brief UNUSED find which view column shows filenames

Columns may be in any order
Scans column titles, looking for "0"

@param view data structure for the view to which the file list belongs

@return integer column number, 0 to MAX_COLUMNS-1
*/
/* gint e2_fileview_find_name_col (ViewInfo *view)
{
	GList *cols = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->treeview));
	GList *base = cols;
	G_CONST_RETURN gchar *title;
	gint result = 0;
	for ( ; cols != NULL; cols = cols->next )
	{
		title = gtk_tree_view_column_get_title (cols->data);
		if (g_str_equal (title, "0"))  break;
		result++;
	}
	g_list_free (base);
//	return (result < MAX_COLUMNS) ? result : -1;
	return result;
} */
/**
@brief exchange active and inactive panes

This function is called from e2_pane_activate_other (), i.e.
whenever we need to exchange curr_view and other_view.
It toggles pointers and changes colours
The latter is done by interrogating the selections in both file views,
and so this fn should be blocked if either view is still being
populated at the time this is called ?? CHECKME

@return
*/
void e2_fileview_switch_views (void)
{
	ViewInfo *temp;

#ifdef E2_VFSTMP
	//FIXME what dir to open other_view->dir may be virtual, as indicated by view->spacedata
	//add some func like e2_fs_chdir (view);
	if (other_view->spacedata != NULL)
	{
		if (e2_fs_chdir_local (other_view->spacedata->workplace E2_ERR_NONE()))
			return;
	}
	else
#else
	if (!e2_fs_chdir (other_view->dir E2_ERR_NONE()))
#endif
		return;

	temp = other_view;
	other_view = curr_view;
	curr_view = temp;

	e2_pane_flag_active ();	//change the status-indicator for the panes
}
/* *
@brief UNUSED swap view pointers

Exchange curr_view and other_view,
without doing anything else

@return
*/
/*void e2_fileview_switch_views_simple (void)
{
	ViewInfo *temp = other_view;
	other_view = curr_view;
	curr_view = temp;
}*/
/**
@brief get indices of top line and left column shown in a filelist treeview
This gets scroll info for treeview rebuilds
@param view data structure for the view to be changed
@param leftcol pointer to store for the left column index
@param toprow pointer to store for the top row index

@return
*/
void e2_fileview_get_scroll_data (ViewInfo *view, gint *leftcol, gint *toprow)
{
	GtkTreePath *startpath;
	GtkTreeViewColumn *startcol;
//	gint cell_x, cell_y;
	if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview),
		0, 0, &startpath, &startcol, NULL, NULL))	//&cell_x, &cell_y))
	{
		*toprow = *gtk_tree_path_get_indices (startpath);
		printd (DEBUG, "path found, row is %d", *toprow);
		gtk_tree_path_free (startpath);
	}
	else	//liststore is empty
		*toprow = 0;
//gtk bug, startcol always is the one at index 0 (maybe fixed after 2.8.6)
	*leftcol = 0;
}
/**
@brief scroll a filelist treeview to top line @a toprow and left column @a leftcol
This is used in treeview rebuilds
It expects that the treeview exists - i.e. be careful when threaded
Assumes BGL is closed
@param view data structure for the view to be changed
@param leftcol left column index
@param toprow top row index

@return
*/
void e2_fileview_scroll_to_position (ViewInfo *view, gint leftcol, gint toprow)
{
	if (toprow > 0 || leftcol > 0)
	{
		GtkTreeView *tvw = GTK_TREE_VIEW (view->treeview);
		GtkTreeModel *model = gtk_tree_view_get_model (tvw);
		GtkTreeViewColumn* col = NULL;
		GtkTreePath* path = NULL;
		if (toprow > 0)
		{
			GtkTreeIter iter;
			if (gtk_tree_model_iter_nth_child (model, &iter, NULL, toprow))
				path = gtk_tree_model_get_path (model, &iter);
		}
		else
			path = gtk_tree_path_new_first ();

//		WAIT_FOR_EVENTS
		gtk_tree_view_scroll_to_cell (tvw, path, col, TRUE, 0, 0);
		gtk_tree_path_free (path);
	}
}
/**
@brief helper function to clean up redundant tree hash tables
 when there's nothing better to do
@param data pointer to table to be destroyed
@return FALSE so that this is not called again
*/
gboolean e2_fileview_treehash_free (GHashTable *data)
{
	g_hash_table_destroy (data);
	return FALSE;
}
/**
@brief get hash of selected-item names for pane in @a view

Logged names are in utf-8 encoding, dirs have trailing /
It is used as part of the view refresh or recreate processes
(even with FAM, needed for refresh action, (un)hide, (un)filter,
 window re-create)

@param view data structure for view being processed

@return hash table of names, NULL if nothing selected
*/
//CHECKME filter & child
GHashTable *e2_fileview_log_selected_names (ViewInfo *view)
{
	GtkTreeModel *model;
	GList *base = gtk_tree_selection_get_selected_rows (view->selection, &model);
	if (g_list_length (base) == 0)
		return NULL;

	//build names-hash
	GHashTable *treehash = g_hash_table_new_full
			(g_str_hash, g_str_equal, g_free, NULL);
	GtkTreeIter iter;
	gchar *itemname;
	GList *selected_paths;

	for (selected_paths=base; selected_paths!=NULL; selected_paths=selected_paths->next)
	{
		if (gtk_tree_model_get_iter (model, &iter, selected_paths->data))
  		{
			gtk_tree_model_get (model, &iter, FILENAME, &itemname, -1);
			g_hash_table_insert (treehash, itemname, NULL);
		}
		gtk_tree_path_free (selected_paths->data);
	}
	g_list_free (base);
	return treehash;
}
/**
@brief re-select items in list store for specified view

@a view ->selected_names is freed here

@param view data structure for view being processed

@return
*/
void e2_fileview_reselect_names (ViewInfo *view)
{
//	GtkTreeModel *model = GTK_TREE_MODEL (view->store);
	//may be filter model
	GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview));
	GtkTreeIter iter;
	gboolean notempty = gtk_tree_model_get_iter_first (model, &iter);
	GHashTable *oldselected = view->selected_names;
	if (oldselected != NULL && notempty)
	{
		guint more_hits = g_hash_table_size (oldselected);
		GtkTreeSelection *sel = view->selection;
		gchar *itemname;
		gpointer key;
		gpointer value;
		do
		{
			gtk_tree_model_get (model, &iter, FILENAME, &itemname, -1);
			if (g_hash_table_lookup_extended (oldselected, itemname,
					&key, &value))
			{
				gtk_tree_selection_select_iter (sel, &iter);
				more_hits--;
			}
			g_free (itemname);
		} while (more_hits > 0 && gtk_tree_model_iter_next (model, &iter));
	}

	if (oldselected != NULL)
	{
		//setup to get rid of hash when there's more time
		g_idle_add_full (G_PRIORITY_LOW,
			(GSourceFunc) e2_fileview_treehash_free, oldselected, NULL);
		view->selected_names = NULL;
	}
}
/**
@brief cleanup helper function

@param info data item to clean
@param data UNUSED

@return
*/
static void _e2_fileview_cleaninfo (FileInfo *info, gpointer data)
{
	DEALLOCATE (FileInfo, info);
}
/**
@brief convert @a entries from a list of localised heaped strings to a list of FileInfo's
Upon failure, the suppiied list is cleared and its ptr set to relevant error code
@param parentpath absolute path of dir containing items in @a list, localised string
@param list store for ptr to Glist of heaped item names

@return TRUE if the list was converted successfully
*/
static gboolean _e2_fileview_make_all_infos (gchar *parentpath, GList **list)
{
	gint len1, len2, trailer;
	gchar *item;
	FileInfo *info;
	GList *member;
	guint errval = E2DREAD_ENOENT;	//assign to prevent compiler error
#ifdef E2_VFS
	VPATH data;
	data.spacedata = NULL; //FIXME
#endif
#ifdef E2_VFSTMP
	get proper spacedata
#endif

	len1 = strlen (parentpath);
	trailer = (*(parentpath + len1 - sizeof (gchar)) == G_DIR_SEPARATOR) ? 0 : sizeof (gchar); //ascii ok
	len2 = (len1 + trailer + NAME_MAX)/8*8 + 8;

	gchar localpath[len2];
	g_strlcpy (localpath, parentpath, sizeof (localpath));
	if (trailer)
	{
		*(parentpath + len1 - sizeof (gchar)) = G_DIR_SEPARATOR;
		len1 += sizeof (gchar);
	}
	len2 -= len1;	//or NAME_MAX ?
	item = localpath + len1;

	//retval = TRUE;
	for (member = *list; member != NULL; member = member->next)
	{
#ifdef USE_GLIB2_10
		info = (FileInfo *) g_slice_alloc (sizeof (FileInfo));
//		info = ALLOCATE (FileInfo);
#else
		info = ALLOCATE (FileInfo);
#endif
		CHECKALLOCATEDWARN (info, )
		if (info == NULL)
		{
			errval = E2DREAD_ENOMEM;
			goto errexit;
		}
nextmember:
		g_strlcpy (item, (gchar *)member->data, len2);
#ifdef E2_VFS
		data.localpath = localpath;
		if (e2_fs_lstat (&data, &info->statbuf E2_ERR_NONE()))
#else
		if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE()))
#endif
		{
			//FIXME use error code from prior lstat to do this parent-check better
			*item = '\0';
			if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE()))
			{
				errval = E2DREAD_NS;
				goto errexit;
			}
			else
			{
				//keep going if just this one item is N/A
				GList *tmp = member;
				member = member->next;
				*list = g_list_delete_link (*list, tmp);
				if (member != NULL)
					goto nextmember;
				DEALLOCATE (FileInfo, info);
				break;
			}
		}
		//any truncation here (>NAME_MAX+1) means it will never be able to be stat'd again
		g_strlcpy (info->filename, (gchar *)member->data, sizeof (info->filename));
		g_free (member->data);
		member->data = info;
	}

	return TRUE;

errexit:
	//FIXME warning
	e2_list_free_with_data (&member);	//get rid of remaining string data
	g_list_foreach (*list, (GFunc) _e2_fileview_cleaninfo, NULL);
	g_list_free (*list);
	*list = GINT_TO_POINTER (errval);
	return FALSE;
}
/**
@brief find matching path entry in history list

@param list Glist of past path strings
@param path path string to be matched

@return list entry whose data matches path, or else NULL
*/
static GList *_e2_fileview_find_history (GList *list, gchar *path)
{
	GList *tmp;
	for (tmp = list; tmp != NULL; tmp = tmp->next)
	{
		if (g_str_equal (((E2_DirHistoryEntry *) tmp->data)->path, path))
		  return tmp;
	}
	return NULL;
}
/**
@brief update all items' info in the treeview associated with @a view

After updating the underlying liststore contents, item(s) are
(re) selected as appropriate, and the view is scrolled to the
relevant position
This is used for refreshes and cd's
It can be a thread func - though not yet thread-safe

Directory whose content we want is named in view->dir (utf-8)
Current store content is cleared after successful preliminary things,
but before new data is added, of course
Active filter(s) are applied, but no sorting.
If relevant, the text-color for selected items is updated here, rather
than in the selection function (too many un-necessary repeats, there)
This function is not necessarily thread-safe
Assumes BGL is closed upon arrival here
NOTE sets view->total_items, and view->row if needed

@param view data structure for the view to which the file list belongs

@return TRUE if all was completed
*/
gboolean e2_fileview_prepare_list (ViewInfo *view)
{
//	printd (DEBUG, "e2_fileview_prepare_list");
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, prepare list");
#endif
	e2_filelist_disable_refresh ();
	e2_window_set_cursor (GDK_WATCH);
#ifdef E2_VFS
	VPATH data;
	data.spacedata = view->spacedata;
#endif
	GtkTreeModel *mdl;
	GtkTreeSortable *sortable;
	GList *entries;	//list of FileInfo's created when reading dir
// GObject *debug = G_OBJECT (view->model);
//#ifdef E2_NEWREFRESH
	if (view->listcontrols.refreshtype == E2_CHANGE)
//#else
//	if (view->listcontrols.refreshtype == E2_REFRESH || view->listcontrols.refreshtype == E2_CHANGE)
//#endif
	{
//========== EXPERIMENTAL STORE-COPY PROCESS
#ifdef STORECOPY
//CHECKME rationalise code; confirm that filtering; refreshing are ok
		if (g_str_equal (curr_view->dir, other_view->dir)
			&& curr_view->spacedata == other_view->spacedata)
		{
			ViewInfo *otherview = (view == curr_view) ? other_view:curr_view;
			//match only older refreshes
//			if (view->refreshtype == E2_CHANGE || otherview->dir_mtime < time (NULL))
//			{
				printd (DEBUG, "copy other store");
				GtkListStore *newstore = e2_filelist_copy_store (otherview->store);
				g_object_ref (G_OBJECT (view->store));
				//this has same effect as unref existing filtermodel
				gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), NULL);
				//arrange to cleanup the old store at an idle time
				gboolean newtimer = (app.used_stores == NULL);
				app.used_stores = g_slist_append (app.used_stores, view->store);
				if (newtimer)
				{
					printd (DEBUG, "setup to clear stores later");
					g_idle_add (e2_filelist_clear_old_stores, NULL);
				}

				view->store = newstore;
				view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (newstore), NULL);
				g_object_unref (G_OBJECT (newstore));

				//apply current filtering
				view->filtered_before = FALSE;	//trigger re-attachment of filter func
				e2_fileview_refilter_list (view);

				//continue the current sorting arrangements
				sortable = GTK_TREE_SORTABLE (newstore);
				//allow non-sorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID
		//		gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL);
				gint array_row = (view == app.pane1.view) ? 0 : 1;
				gint *order_array = displayed_col_order[array_row];
				gint i, m;
				for (i = 0; i < MAX_COLUMNS; i++)
				{
					m = order_array[i];  //get the model/'external' index
					if (m == FILENAME && view->extsort)
						gtk_tree_sortable_set_sort_func (sortable, FILENAME,
							(GtkTreeIterCompareFunc) e2_fileview_ext_sort,
								&view->sort_order, NULL);
					else
						gtk_tree_sortable_set_sort_func (sortable, m,
							e2_all_columns[m].sort_func, &view->sort_order, NULL);
				}
				//sort the store using the current sort column & direction
				gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column,
					view->sort_order);
				//make it fully ours
				gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model);
//				g_object_unref (G_OBJECT (view->model)); BAD
				//setup for status-line counters
				view->total_items = otherview->total_items;

				view->case_sensitive_names = otherview->case_sensitive_names;
				view->dir_mtime = otherview->dir_mtime;
				view->dir_ctime = otherview->dir_ctime;
				e2_window_set_cursor (GDK_LEFT_PTR);
				e2_filelist_enable_refresh ();
				return TRUE;
//			}
//			printd (DEBUG, "time-test failed");
		}
//		else	//panes have different dirs, or some error in copying
//		{
			printd (DEBUG, "use fresh store");
#endif //def STORECOPY
//============
			gchar *local = F_FILENAME_TO_LOCALE (view->dir);
			//construct relevant items data for store-filler
			//FIXME if we can, just get relevant items for a refresh
			//get a fresh set of FileInfo data from source
	//	printd (DEBUG, "grab current directory data");
#ifdef E2_VFS
			data.localpath = local;
			gdk_threads_leave ();
			entries = (GList *)e2_fs_dir_foreach (&data,
#else
			gdk_threads_leave ();
			entries = (GList *)e2_fs_dir_foreach (local,
#endif
				E2_DIRWATCH_CHECK,	//this is irrelevant for non-local dirs
				NULL, NULL, NULL E2_ERR_NONE());
//			printd (DEBUG, "Read dir function returned %x", entries);
			gdk_threads_enter ();

			if (E2DREAD_FAILED (entries) || !_e2_fileview_make_all_infos (local, &entries))
			{
				//any cleanup of entries (possibly with mixed data types) done downstream
				F_FREE (local);
				e2_window_set_cursor (GDK_LEFT_PTR);
#ifdef E2_VFSTMP
				//FIXME warn user
#endif
#ifdef E2_REFRESH_DEBUG
		printd (DEBUG, "enable refresh, read dir function error");
#endif
				e2_filelist_enable_refresh ();
//				LISTS_LOCK
//				view->listcontrols.listing = FALSE;
//				LISTS_UNLOCK
				return FALSE;
			}
/*		LISTS_LOCK
		gboolean abort = (view->listcontrols.newpath != NULL);	//different dir wanted now
		LISTS_UNLOCK
		if (abort)
		{
			STOP NOW
		}
*/
		printd (DEBUG, "current directory data loaded");
			//even when FAM is working, some things (e.g. treedialog) must poll for
			//changes, so set times reflecting the completed poll
			struct stat sb;
#ifdef E2_VFS
			data.localpath = local;
			if (e2_fs_stat (&data, &sb E2_ERR_NONE()))	//through links
#else
			if (e2_fs_stat (local, &sb E2_ERR_NONE()))	//through links
#endif
			{
				printd (WARN, "Unable to stat directory: %s", view->dir);
				F_FREE (local);
				e2_window_set_cursor (GDK_LEFT_PTR);
				e2_filelist_enable_refresh ();
				return FALSE;
			}
			view->case_sensitive_names = E2FSCASE_UNKNOWN;	//after cd, can be replaced by cached value
			view->dir_mtime = sb.st_mtime;
			view->dir_ctime = sb.st_ctime;

/*#ifndef E2_NEWREFRESH
# ifdef E2_VFSTMP
			if (view->spacedata != NULL && view->spacedata->monitored)
			{ 	//v-dir is monitored
				//FIXME
			}
			else if (view->spacedata != NULL)
			{
# endif
# ifdef E2_FAM_KERNEL
			if (view->refreshtype == E2_REFRESH)
				e2_fs_FAM_clean_reports (view->dir);
		/ *	prior reports about the dir are now gone.
			Henceforth any report about the dir or an item in it
			will trigger a new refresh, even if the change that
			caused the report is shown in this refresh.
			The alternative is to do this at the end, and lose any
			change after an item is processed in this refresh * /
# endif	//which fam
# ifdef E2_VFSTMP
			}
# endif
#endif	//ndef E2_NEWREFRESH
*/
	//		mdl = GTK_TREE_MODEL (view->store);
			sortable = GTK_TREE_SORTABLE (view->store);
			//remember the current sorting arrangements
			gtk_tree_sortable_get_sort_column_id (sortable, &view->sort_column,
				&view->sort_order);
			//turn off model sorting before any rows are added
			//CHECKME do we really benefit from turning off sorting when the
			//view is detached from the model?
			//GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID (-2) appeared in gtk 2.6
			gtk_tree_sortable_set_sort_column_id (sortable,
				GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
//============
//#ifdef STORECOPY
//		}	//end of block for experimental stuff
//#endif
//============
	}
	else	//doing a window recreation
	{	//create new store with current items, in case content-format is changed
		entries = NULL;
		FileInfo *infoptr, *newinfo;
		GtkTreeIter iter;
		mdl = GTK_TREE_MODEL (view->store);
		if (gtk_tree_model_get_iter_first (mdl, &iter))
		{
			do
			{
				gtk_tree_model_get (mdl, &iter, FINFO, &infoptr, -1);
#ifdef USE_GLIB2_10
				newinfo = (FileInfo *) g_slice_alloc (sizeof(FileInfo));
				g_memmove (newinfo, infoptr, sizeof(FileInfo));
#else
				newinfo = (FileInfo *) g_memdup (infoptr, sizeof(FileInfo));
#endif
				entries = g_list_prepend (entries, newinfo);	//faster
			} while (gtk_tree_model_iter_next (mdl, &iter));
			entries = g_list_reverse (entries);
		}
	}

	GtkListStore *store = e2_filelist_fill_store (entries, view);
//	printd (DEBUG, "new liststore ready");
	if (store != NULL)
	{	//there was a valid store created
		//can't re-assign the filter model to new store, must replace model
		//make sure the store and base model stay alive after the filtermodel goes
		g_object_ref (G_OBJECT (view->store));
		view->model = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
		gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), view->model);
		g_object_unref (G_OBJECT (store));
		g_object_unref (G_OBJECT (view->model));
		//arrange to cleanup the old store at an idle time
		gboolean newtimer = (app.used_stores == NULL);
		app.used_stores = g_slist_append (app.used_stores, view->store);
		if (newtimer)
		{
			printd (DEBUG, "setup to clear stores later");
			g_idle_add (e2_filelist_clear_old_stores, NULL);
		}
		view->store = store;
		sortable = GTK_TREE_SORTABLE (store);
		//allow non-sorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID
//		gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL);
		gint array_row = (view == app.pane1.view) ? 0 : 1;
		gint *order_array = displayed_col_order[array_row];
		gint i, m;
		for (i = 0; i < MAX_COLUMNS; i++)
		{
			m = order_array[i];  //get the model/'external' index
			if (m == FILENAME && view->extsort)
				gtk_tree_sortable_set_sort_func (sortable, FILENAME,
					(GtkTreeIterCompareFunc) e2_fileview_ext_sort,
						&view->sort_order, NULL);
			else
				gtk_tree_sortable_set_sort_func (sortable, m,
					e2_all_columns[m].sort_func, &view->sort_order, NULL);
		}

//	printd (DEBUG, "resort new data");
		//sort the view using the current sort column & direction
		gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column,
			view->sort_order);
//	printd (DEBUG, "resort finished");
		//setup for status-line counters
		//FIXME if entries list can be just the changed items
		//entries count adjusted for ".." ("." filtered from list)
		view->total_items = g_list_length (entries) - 1;	//may be -1
		//fuse-created dirs may not have any ".." entry, but we need one
		gboolean addup = FALSE;
		FileInfo *infoptr;
		GtkTreeIter iter;
		//sorting will have placed any existing ".." at start of store
		//FIXME neater to do this is read-dir function
		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
		{
			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, FINFO, &infoptr, -1);
			if (!g_str_equal (infoptr->filename, ".."))
				addup = TRUE;
		}
		else
			addup = TRUE;
		if (addup)
		{
#ifdef USE_GLIB2_10
			infoptr = (FileInfo *) g_slice_alloc (sizeof (FileInfo));
//			infoptr = ALLOCATE (FileInfo);
#else
			infoptr = ALLOCATE (FileInfo);
#endif
			CHECKALLOCATEDFATAL (infoptr);
			g_strlcpy (infoptr->filename, "..", sizeof (infoptr->filename));
#ifdef E2_VFSTMP
			//FIXME properly handle root of v-dir
#endif
#ifdef E2_VFS
			VPATH fake = { G_DIR_SEPARATOR_S, view->spacedata };
			e2_fs_stat (&fake, &infoptr->statbuf E2_ERR_NONE());
#else
			e2_fs_stat (G_DIR_SEPARATOR_S, &infoptr->statbuf E2_ERR_NONE());
#endif
			gtk_list_store_insert_with_values (store, &iter, 0,
				FILENAME, "../",
				SIZE, "0",
				PERM, "drwxrwxrwx",
				OWNER, "",
				GROUP, "",
				MODIFIED, "",
				ACCESSED, "",
				CHANGED, "",
				NAMEKEY, g_utf8_collate_key ("..", -1),
				FINFO, infoptr,
				FORECOLOR, e2_option_color_get ("color-ft-dir"),
//				VISIBLE, TRUE,
				-1);
			view->total_items++;
		}
		view->filtered_before = FALSE;	//trigger re-attachment of filter func
		e2_fileview_refilter_list (view);
	}
//	else //liststore creation error
//		FIXME clear entries data, warn user

	if (entries != NULL)
		g_list_free (entries);	//data in the list is in liststore pointers, freed later

	e2_window_set_cursor (GDK_LEFT_PTR);
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, prepare list");
#endif
	e2_filelist_enable_refresh ();
//	printd (DEBUG, "end prepare list for %s", view->dir);
	return TRUE;
}
/* *
@brief change-directory completion-watch timer-function

@param data pointer to data relating to the cd being monitored

@return FALSE when ready to shutdown timer
*/
/*
gboolean e2_fileview_cd_watch (E2_CDwatch *data)
{
	if (app.timers[CDWAIT_T] > 0)
		return TRUE;	//the cd is still blocked
	//can't use E2_PaneRuntime* directly, due to include circularity problem
	E2_PaneRuntime *rt = (data->view == curr_view) ? curr_pane : other_pane;
	if (!cd_blocked && g_str_has_prefix (rt->path, data->newpath))
	{
		*data->completed_flag = CD_SUCCESS;
		g_free (data->newpath);
		DEALLOCATE (E2_CDwatch, data);
		return FALSE;
	}
	//stop after 100 tries = 10 seconds
	//FIXME use a timeout mechanism consistent with the one for non-local dir reading
	if (++data->repeats == 100)
	{
		*data->completed_flag = CD_TIMEOUT;
		g_free (data->newpath);
		DEALLOCATE (E2_CDwatch, data);
		return FALSE;
	}
	return TRUE;
}
*/
/**
@brief change-directory timer-function
This can be called for both dirs, at session start at least
It handles any requested cd for either pane
@param data pointer to cd data for the change

@return TRUE if prior cd is not complete, or FALSE to shutdown timer
*/
gboolean e2_fileview_cd_manage (E2_Listman *data)
{
	E2_Listman *workdata;
	pthread_t thisID;
	pthread_attr_t attr;
	gboolean one_active, flag;	//, flag2;
	guint fails = 0;

	LISTS_LOCK
	flag = app.pane1_view.listcontrols.cd_requested || app.pane2_view.listcontrols.cd_requested;
	data->cd_requested = TRUE;	//mark this one to block any repeats
	printd (DEBUG, "managing cd to %s", data->newpath);
	LISTS_UNLOCK
	if (flag)
	{
		//this is a timer callback which escaped the termination process below
		//or (unlikely) is for the former-other pane and happened to arrive here
		//at a useless moment
		printd (NOTICE, "aborted cd timer, another one has precedence");	//, data->timer);
		return FALSE;
	}
	//immediately stop the timer which called here, to prevent _any_ chance of
	//parallel instances
	while (g_source_remove_by_user_data (data))
	{
		printd (DEBUG, "shut down a cd-timer for this pane");
	}
//#ifndef E2_STATUS_DEMAND
//	e2_window_disable_status_update ();	//prevents bad behaviour ??
//#endif
	//in case the active pane changes while we're here, get a snapshot
	one_active = (curr_view == &app.pane1_view);
	do
	{
		//try active pane first
		workdata = (one_active) ? &app.pane1_view.listcontrols : &app.pane2_view.listcontrols;
		LISTS_LOCK
		if (workdata->cd_requested)
		{
			if (!(workdata->cd_working || workdata->refresh_working
#ifdef E2_STATUS_BLOCK
			|| app.status_working
#endif
			))
			{
				LISTS_UNLOCK
				printd (NOTICE, "Changing active pane to %s", workdata->newpath);
				pthread_attr_init (&attr);
				pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
				if (pthread_create (&thisID, &attr,
					(gpointer) _e2_fileview_change_dir, workdata) == 0)
				{
					printd (DEBUG,"change-dir-thread (ID=%lu) started", thisID);
					//do this here too in case the thread gets underway later
					LISTS_LOCK
					workdata->cd_requested = FALSE;
					LISTS_UNLOCK
				}
				else
					fails++;
			}
			else	//busy
				if ( (one_active && app.pane2_view.listcontrols.cd_working)
				  ||(!one_active && app.pane1_view.listcontrols.cd_working) )
			{
//				if ( (one_active && app.pane2_view.listcontrols.timer == 0)
//				  ||(!one_active && app.pane1_view.listcontrols.timer == 0) )
//				{	//there is no active timer for the other pane (should never happen)
					printd (DEBUG,"active pane blocakge, restarting timer");
					//data->timer =
					g_timeout_add_full (G_PRIORITY_HIGH, 90,
						(GSourceFunc) e2_fileview_cd_manage, data, NULL);
//				}
				LISTS_UNLOCK
				return FALSE;	//wait for a timer to call back here
			}
			else
			{
				LISTS_UNLOCK
			}
		}
		else
		{
			LISTS_UNLOCK
		}

		//then try inactive pane
		workdata = (one_active) ? &app.pane2_view.listcontrols : &app.pane1_view.listcontrols;
		LISTS_LOCK
		if (workdata->cd_requested)
		{
			if (!(workdata->cd_working || workdata->refresh_working
#ifdef E2_STATUS_BLOCK
			|| app.status_working
#endif
			))
			{
				LISTS_UNLOCK
				printd (NOTICE, "Changing INactive pane to %s", workdata->newpath);
				pthread_attr_init (&attr);
				pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
				if (pthread_create (&thisID, &attr,
					(gpointer) _e2_fileview_change_dir, workdata) == 0)
				{
					printd (DEBUG,"change-dir-thread (ID=%lu) started", thisID);
					LISTS_LOCK
					workdata->cd_requested = FALSE;
					LISTS_UNLOCK
				}
				else
					fails++;
			}
			else	//busy
				if ( (one_active && app.pane1_view.listcontrols.cd_working)
				  ||(!one_active && app.pane2_view.listcontrols.cd_working) )
			{
//				if ( (one_active && app.pane1_view.listcontrols.timer == 0)
//				  ||(!one_active && app.pane2_view.listcontrols.timer == 0) )
//				{	//there is no active timer for the other pane (should never happen)
					printd (DEBUG,"INactive pane blocakge, restarting timer");
					//data->timer =
					g_timeout_add_full (G_PRIORITY_HIGH, 90,
						(GSourceFunc) e2_fileview_cd_manage, data, NULL);
//				}
				LISTS_UNLOCK
				return FALSE;	//wait for a timer to call back here
			}
			else
			{
				LISTS_UNLOCK
			}
		}
		else
		{
			LISTS_UNLOCK
		}

		LISTS_LOCK
		flag = app.pane1_view.listcontrols.cd_requested || app.pane2_view.listcontrols.cd_requested;
		LISTS_UNLOCK
	} while (flag && fails < 2);

	if (flag && fails == 2)
	{
//		gdk_threads_enter ();
//		gchar *msg = ;
//		e2_output_print_error (msg, FALSE);
//		gdk_threads_leave ();
		printd (WARN, "CD-thread creation error");
	}

//#ifndef E2_STATUS_DEMAND
//	printd (DEBUG,"resume status checking");
//	e2_window_enable_status_update (-1);
//#endif
//	LISTS_LOCK
//	data->timer = 0;	//now it's ok to allow other timers
//	LISTS_UNLOCK

	return FALSE;
}
/**
@brief get new directory contents for a view
This is a thread-function.
Assumes that the "big gtk lock" (gdk mutex) is off/open

@param cddata data structure with parameters for the view to be changed

@return NULL always
*/
static gpointer _e2_fileview_change_dir (E2_Listman *cddata)
{
	E2_DirHistoryEntry *entry;
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, _e2_fileview_change_dir");
#endif
	e2_filelist_disable_one_refresh ((cddata == &app.pane1_view.listcontrols) ?
		PANE1:PANE2); //suspend overlapping refreshes ASAP
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread
	LISTS_LOCK
	gchar *newpath = cddata->newpath;	//duplicate in case user cd's again, free it if cd fails
	cddata->newpath = NULL;	//from now, non-NULL signals a new cd request
	//CHECKME ok to overlap calles, hence defer setting these flags ?
	cddata->cd_working = TRUE;	//prevent re-entrant cd's
	cddata->cd_requested = FALSE;	//this may preceed the change in e2_fileview_cd_manage()
	ViewInfo *view = cddata->view;
	gboolean history = cddata->history;
	gboolean hook = cddata->hook;
	LISTS_UNLOCK
	printd (DEBUG, "_e2_fileview_change_dir: %s", newpath);
/*
#ifdef E2_VFSTMP
	//FIXME better check for usable dir
	//also, handle vfs flags and local dirs that only use async IO
	//some OS have MNT_SYNCHRONOUS and MNT_ASYNC in a statfs.f_flags
	if (view->spacedata == NULL)
	{	//not a v-dir
#endif
		if (e2_fs_dir_is_native (newpath))
			view->dirtype = FS_LOCAL;
		else
			view->dirtype = FS_FUSE;
#ifdef E2_VFSTMP
	}
#endif
*/

#ifdef E2_VFSTMP
	//FIXME check that e2_fs_cd_isok works for v-dirs
#endif
	gdk_threads_enter ();	//e2_fs_cd_isok () may display message
	if (! e2_fs_cd_isok (newpath E2_ERR_NONE())) //can't go there CHECKME no delay when non-mounted ?
	{
		gdk_threads_leave ();
		g_free (newpath);
		LISTS_LOCK
		cddata->cd_working = FALSE;
		LISTS_UNLOCK
		e2_filelist_enable_one_refresh ((view == &app.pane1_view) ? PANE1:PANE2);
		return NULL;
	}
	gdk_threads_leave ();
	//update row history for current dir
	//at session-start, view->dir = "", hence no match found in list
	//this update will be redundant if the cd fails or is aborted, but that's ok
	//we do it now anyway, to take up some time in case a competing activity is underway
#ifdef E2_VFSTMP
	//CHECKME confirm v-dir history list is still in place
#endif
	GList *outdir = _e2_fileview_find_history (view->dir_history, view->dir);
	if (outdir != NULL)
	{
		entry = outdir->data;
		/*even though the sort-order and/or dir contents
		  might change before we re-visit here,
		  if there is a selection now, remember its start,
		  or else remember the start of the current display*/

		/*this func may be called during a window-rebuild, in which
		   case the old dir will the same as the new one, and the
		   selection and visible rect data will be meaningless*/

		GtkTreePath *startpath;
		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->treeview),
			0, 0, &startpath, NULL, NULL, NULL))
		{
			entry->toprow = *gtk_tree_path_get_indices (startpath);
			gtk_tree_path_free (startpath);
		}
		else	//dir is empty
			entry->toprow = 0;

		GtkTreeSelection *sel = view->selection;
		if (gtk_tree_selection_count_selected_rows (sel) > 0)
		{
			GtkTreeModel *model;
			GList *selpaths = gtk_tree_selection_get_selected_rows
				(sel, &model);
			//get the 1st selected item's row
			entry->selrow = *gtk_tree_path_get_indices (selpaths->data);
			g_list_foreach (selpaths, (GFunc) gtk_tree_path_free, NULL);
			g_list_free (selpaths);
		}
		else
			entry->selrow = -1;

		if (view->case_sensitive_names & E2FSCASE_NOCACHE)
			entry->case_sensitive_names = E2FSCASE_UNKNOWN;
		else
			entry->case_sensitive_names = view->case_sensitive_names;
	}

/*checks for overlapping cd/refresh/recreate now done externally
	//pause until any in-progess e2_fileview_prepare_list() is completed
	//CHECKME should this involve some test for actual-treeview-completion ?
	LISTS_LOCK
	if (cddata->listing)
	{
		LISTS_UNLOCK
		printd (DEBUG, "waiting for completion of another treeview update before changing dir");
		app.timers[REFRESHWAIT_T] = g_timeout_add (100,
			(GSourceFunc) _e2_fileview_check_completion, &cddata->listing);
		gtk_main ();
	}
	LISTS_UNLOCK
*/
	GHashTable *selnames = view->selected_names;	//remember in case cd fails
	view->selected_names = NULL;	//signal no need to reselect anything

	//now update the local path store, with backup in case of error
	gchar *oldpath = g_strdup (view->dir);
//E2_VFSTMPOK
	LISTS_LOCK
	g_strlcpy (view->dir, newpath, sizeof (view->dir));
	cddata->refreshtype = E2_CHANGE;	//signal for list creator
	LISTS_UNLOCK

	gdk_threads_enter ();
	//get the replacement filelist
	if (!e2_fileview_prepare_list (view))
	{
		gdk_threads_leave ();
		printd (WARN, "prepare list failed");
		view->selected_names = selnames;
		g_strlcpy (view->dir, oldpath, sizeof (view->dir));
		g_free (oldpath);
		g_free (newpath);
	}
	else
	{
//		printd (DEBUG, "prepare list succeeded");
		g_free (oldpath);
		E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane;
		if (rt->toolbar.has_command_line)
			//update dirline to new path
			e2_command_line_change_dir (newpath, rt);

		//scroll to approximately the position from last time
		view->row = 0;	//default position
		GtkTreeModel *mdl = view->model;
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter_first (mdl, &iter))
		{
#ifdef E2_VFSTMP
	//CHECKME confirm v-dir history list is still in place
#endif
			GList *indir = _e2_fileview_find_history (view->dir_history, view->dir);
			if (indir != NULL)
			{  //new dir is in history list
				GtkTreePath *path;
				entry = indir->data;

				view->case_sensitive_names = entry->case_sensitive_names;

				if (entry->selrow > -1)
				{
					//during unpacking of an archive etc all data from last time may not be there
					//don't select or scroll if not enough data now (CHECKME items - 1 instead ?
					gint items =
					//if (
						gtk_tree_model_iter_n_children (mdl, NULL);
					if (items	//0,1,... 1-based
						<= entry->selrow //0,1,... 0-based
						)
					{
//						entry->selrow = -1;
						if (items > 0)
						{
							e2_fileview_focus_row (view, items-1, FALSE, FALSE, FALSE, FALSE);
						}
					}
					else

					if (entry->selrow > -1)
					{
						//select the 1st row we selected last time
						//(tuff luck if there's been content change or sort-order change)
						//can't use e2_fileview_focus_row () as that is bad near end of list
						//move back past any filtered-out iter
						while (!gtk_tree_model_iter_nth_child (mdl, &iter, NULL, entry->selrow)
							&& entry->selrow > -1)
								entry->selrow --;
						if (entry->selrow > -1)
						{
							view->row = entry->selrow;
							path = gtk_tree_model_get_path (mdl, &iter);
							if (view->row > 0)
							{
								//to avoid bad scroll-behaviour near the end of the list, need
								//to scroll in 2 steps ...
								gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->treeview),
									path, NULL, TRUE, 1.0, 0.0);
								WAIT_FOR_EVENTS
/*FIXME does nothing cuz value is 0
								GtkAdjustment *vadj = gtk_tree_view_get_vadjustment
									(GTK_TREE_VIEW (view->treeview));
								gdouble value = gtk_adjustment_get_value (vadj);
								gdouble newvalue = value - vadj->page_size * 0.382;
								gtk_adjustment_set_value (vadj, newvalue);
*/
							}
							gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview),
								path, NULL, FALSE);
							gtk_tree_path_free (path);
						}
						else
						{
							view->row = 0;	//default value
						}
					}
				}
				else if (entry->toprow > 0)
				{
					//goto (approximately) the top of the visible window as it was last time
					while (!gtk_tree_model_iter_nth_child (mdl, &iter, NULL, entry->toprow)
						&& entry->toprow > 0)
							entry->toprow --;
					if (entry->toprow > 0)
					{
						path = gtk_tree_model_get_path (mdl, &iter);
						gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->treeview),
							path, NULL, TRUE, 0, 0);
						gtk_tree_path_free (path);
					}
					e2_fileview_focus_row (view, entry->toprow, FALSE, FALSE, FALSE, FALSE);
				}
			}
			else //the new dir has never been opened in this session
			{
				gboolean flag = e2_option_bool_get ("select-first-item");
				e2_fileview_focus_row (view, 0, flag, FALSE, FALSE, FALSE);
			}
		}

		gdk_threads_leave ();

#ifdef E2_STATUS_REF
		extern gint statref_count;
#endif
		if (view == curr_view
#ifdef E2_STATUS_REF
			&& statref_count == 0
#endif
#ifdef E2_STATUS_BLOCK
			&& !app.status_working
#endif
		)
			e2_window_update_status_bar (NULL);	//expects BGL off

		//ancillary tasks

		if (selnames != NULL)
		{
			//setup to get rid of any old hash
			g_idle_add_full (G_PRIORITY_LOW,
				(GSourceFunc) e2_fileview_treehash_free, selnames, NULL);
		}

		//this is calculated instead of passed, as circularity prevents a
		//E2_PaneRuntime * in any declaration in e2_fileview.h
//		E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane;
		//clear any pending refresh hanging around from the old dir
		//CHECKME kernel reports too ?
		LISTS_LOCK
		cddata->refresh_requested = FALSE;
		LISTS_UNLOCK
		//change FAM place ASAP
#ifdef E2_VFSTMP
		//v-dir monitoring when supported
		if (view->spacedata != NULL && view->spacedata->monitored)
		{
			LISTS_LOCK
			oldpath = rt->path;
			rt->path = newpath;	//DON'T FREE ME
			LISTS_UNLOCK
			//startup alteration-monitoring for the new dir
			//(or if need be, for its target if it's a link)
	//FIXME e2_vfs_?? (oldpath, rt);
			g_free (oldpath);
		}
		else if (view->spacedata == NULL)
		{	//local dir
#endif
#ifdef E2_FAM
			oldpath = rt->path;
			LISTS_LOCK
			rt->path = newpath;	//DON'T FREE ME
			LISTS_UNLOCK
			//startup alteration-monitoring for the new dir
			//(or if need be, for its target if it's a link)
			e2_fs_FAM_change (oldpath, rt);
			g_free (oldpath);
#else
			//update the path store
			LISTS_LOCK
			g_free (rt->path);
			rt->path = newpath;
			LISTS_UNLOCK
#endif	//def E2_FAM
#ifdef E2_VFSTMP
		}
#endif

		//run refocus hooklist (BGL open)
		if (view == curr_view)
		{
#ifdef E2_VFSTMP
			//CHECKME hook for v-dir
#else
			e2_hook_list_run (&app.hook_pane_focus_changed, curr_pane);
#endif
		}

		//run cd hooklist (BGL open)
		if (hook
#ifdef E2_VFSTMP
	//CHECKME mkdir dialog for v-dir's
			&& (view->spacedata != NULL
			|| view->spacedata->dirtype == FS_LOCAL
			|| view->spacedata->dirtype == FS_FUSE)
#endif
			)
		{
#ifdef E2_VFSTMP
			//FIXME hook process for v-dir's
#else
			e2_hook_list_run (&rt->hook_change_dir, newpath);	//not rt->path if that can be altered in any other thread
#endif
		}

		GList *tmp;
#ifdef E2_VFSTMP
		//CHECKME relevant list for v-dir still in place
#endif
//		printd (DEBUG, "_e2_fileview_change_dir update history");
		//update goto-line-after-opening data
		if ((tmp = _e2_fileview_find_history (view->dir_history, view->dir)) == NULL)
		{  //need a new entry for the history list
			entry = ALLOCATE0 (E2_DirHistoryEntry);
			CHECKALLOCATEDWARNT (entry, );
			if (entry != NULL)
			{
				//just set the path, for now - other data set when dir is departed
				g_strlcpy (entry->path, view->dir, sizeof(entry->path));
				view->dir_history = g_list_append (view->dir_history, entry);
			}
		}
		else
			entry = tmp->data;
#ifdef E2_VFSTMP
		//CHECKME relevant list for v-dir still in place
#endif
		if (history)
		{
			//update the pane visited-dirs history
			HISTORY_LOCK
			//when this list was cached, independent copies of paths were needed
			//e2_list_update_history (newpath, &rt->opendirs, NULL, 30, TRUE);
			//now ...
			//if new entry is already in history, just adjust the index
			if ((tmp = g_list_find_custom (rt->opendirs, entry, (GCompareFunc)e2_list_strcmp)) != NULL)
				rt->opendir_cur = g_list_position (rt->opendirs, tmp);
			else
			{
				if (rt->opendirs != NULL && rt->opendir_cur > 0)
					//split off items before (i.e. visited after) current, and free them
					e2_list_nth_break (&rt->opendirs, rt->opendir_cur, NULL, &rt->opendirs);

				rt->opendirs = g_list_prepend (rt->opendirs, entry);	//treat as a string

				if (rt->opendir_cur == 0)
				{	//list is one longer now
					if (g_list_length (rt->opendirs) > 30)
					{
						tmp = g_list_last (rt->opendirs);
						rt->opendirs = g_list_delete_link (rt->opendirs, tmp);
					}
				}
				else	//at least one member of list was removed before adding this one
					rt->opendir_cur = 0;	//and this is now current
			}
			HISTORY_UNLOCK
		}
	}

	LISTS_LOCK
	cddata->cd_working = FALSE;
	LISTS_UNLOCK
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, change-dir");
#endif
	e2_filelist_enable_one_refresh ((view == &app.pane1_view) ? PANE1:PANE2);

	printd (DEBUG, "_e2_fileview_change_dir ends");
	return NULL;
}
//#ifdef E2_NEWREFRESH
/* *
@brief idle function to run refresh hooklist
NOTE if any registered hook-function does anything to the filesystem that
triggers another refresh, then an infinite loop will happen !
So this is probably not much use ...
@param view pointer to data struct for the view whose filelist was refreshed

@return FALSE, to stop the callbacks
*/
/*static gboolean _e2_fileview_run_hooks (ViewInfo *view)
{
	WATCH OUT FOR REENTRANT CALLING THIS ?
	e2_hook_list_run (&view->hook_refresh, view);
	return FALSE;
} */
/**
@brief incrementally refresh list store for @a view
This is a thread function
If the directory associated with @a view is no longer accessible,
a suitable alternative is found and displayed.
Expects BGL to be open, on arrival here

@param view data structure for view being processed

@return NULL (FALSE) if the refresh fails (or cd to elsewhere), pointerised 1 if it succeeds
*/
gpointer e2_fileview_refresh_list (ViewInfo *view)
{
	gboolean busy;
	LISTS_LOCK
	busy = view->listcontrols.norefresh || view->listcontrols.refresh_working;
	LISTS_UNLOCK
#ifdef E2_STATUS_BLOCK
	if (!busy)
		busy = (view == curr_view && app.status_working);
#endif
	if (busy)
	{
		printd (DEBUG, "BLOCKED start refresh filelist for %s", view->dir);
		return NULL;	//refresh is disabled, or the view is still being processed from last time
	}
	printd (DEBUG, "start refresh filelist for %s", view->dir);
#ifdef E2_VFSTMP
	if ((curr_view->spacedata == other_view->spacedata)
		&& g_str_equal (curr_view->dir, other_view->dir))
	{
		//setup to process both views in sequence, and clear any refresh_requested flags
	}
#endif

	//<for view 1 and later, maybe, view 2 also ...>

	gchar *utf = g_strdup (view->dir);	//copy, so that view->dir not clobbered
#ifdef E2_VFSTMP
	//FIXME valid path check for v-dirs
	//FIXME get path to goto when when dir is v-dir, may include a space-change
#endif
	gdk_threads_enter ();	//in case of downstream error message
	if (e2_fs_get_valid_path (&utf, TRUE E2_ERR_NONE()))
		g_free (utf);
	else
	{	//desired path not accessible, go to somewhere that is ..
		gchar *msg = g_strdup_printf ("Cannot access %s, going to %s instead",
			view->dir, utf);
#ifdef E2_VFSTMP
	//FIXME cater for any space-change message
#endif
		e2_output_print_error (msg, TRUE);
		gdk_threads_leave ();

		E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane;
#ifdef E2_VFSTMP
		//FIXME if needed
		PlaceInfo *spacedata = NULL;
		e2_pane_change_space_pointer (rt, spacedata);
#endif
		e2_pane_change_dir (rt, utf);
		g_free (utf);
		return GINT_TO_POINTER (2);	//non-NULL to signal no more refresh needed
	}
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, fileview_refresh list");
#endif
	E2_ListChoice pnum = (view == &app.pane1_view) ? PANE1:PANE2;
	e2_filelist_disable_one_refresh (pnum);
	e2_window_set_cursor (GDK_WATCH);
	gdk_threads_leave ();
	LISTS_LOCK
	view->listcontrols.refresh_working = TRUE;
	LISTS_UNLOCK

	gchar *local = F_FILENAME_TO_LOCALE (view->dir);
 	//construct relevant items data for store-filler
	//get a fresh set of directory item-names from source
	//FIXME if possible, just get relevant items for the refresh
//	printd (DEBUG, "grab directory names data");
#ifdef E2_VFS
	VPATH ddata = { local, view->spacedata };
	GList *entries = (GList *)e2_fs_dir_foreach (&ddata,
#else
	GList *entries = (GList *)e2_fs_dir_foreach (local,
#endif
		E2_DIRWATCH_CHECK,	//this is irrelevant for non-local dirs
		NULL, NULL, NULL E2_ERR_NONE());

/*	each ->data in entries is a FileInfo. They are cleared when no longer
	needed. Some go into replacement liststore data and are cleared with
	the store. Others that belong to unchanged items are cleared when it's
	confirmed that they are the same */

	if (!E2DREAD_FAILED (entries))
	{
		guint i, indx, itemcount = g_list_length (entries);
		GList *member;
		E2_RefreshInfo *modes = (E2_RefreshInfo *)
#ifdef USE_GLIB2_10
		//can't use ALLOCATE0 cuz > 1 item
		g_slice_alloc0 (sizeof (E2_RefreshInfo) * itemcount);
#elif defined (USE_GLIB2_8)
		g_try_malloc0 (sizeof (E2_RefreshInfo) * itemcount);
#else
		calloc (itemcount, sizeof (E2_RefreshInfo));
#endif
		CHECKALLOCATEDWARN (modes, ) //FIXME properly clean entries list if allocation failed
		if (modes != NULL)
		{
			//FIXME rationalise this with modes creation
			if (!_e2_fileview_make_all_infos (local, &entries))
			{
				//cleanup of entries (possibly with mixed data types) done downstream
				//don't goto accessible path until next refresh, if any
				F_FREE (local);
#ifdef USE_GLIB2_10
				//can't use DEALLOCATE cuz > 1 item
				g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes);
#else
				g_free (modes);
#endif
				LISTS_LOCK
				view->listcontrols.refresh_working = FALSE;
				LISTS_UNLOCK
				gdk_threads_enter ();
				e2_window_set_cursor (GDK_LEFT_PTR);
				gdk_threads_leave ();
				e2_filelist_enable_one_refresh (pnum);
				return NULL;
			}
			//even when FAM is working, some things (e.g. treedialog) must poll for
			//changes, so set times reflecting the completed poll
			struct stat sb;
#ifdef E2_VFS
			VPATH data = { local, view->spacedata };
			if (e2_fs_stat (&data, &sb E2_ERR_NONE()))	//through links
#else
			if (e2_fs_stat (local, &sb E2_ERR_NONE()))	//through links
#endif
			{
				printd (WARN, "Unable to stat directory: %s", view->dir);
				F_FREE (local);
#ifdef USE_GLIB2_10
				//can't use DEALLOCATE cuz > 1 item
				g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes);
#else
				g_free (modes);
#endif
				LISTS_LOCK
				view->listcontrols.refresh_working = FALSE;
				LISTS_UNLOCK
				gdk_threads_enter ();
				e2_window_set_cursor (GDK_LEFT_PTR);
				gdk_threads_leave ();
				e2_filelist_enable_one_refresh (pnum);
				return GINT_TO_POINTER (3);	//no more refresh needed
			}
			view->dir_mtime = sb.st_mtime;
			view->dir_ctime = sb.st_ctime;

			//construct hash for faster matching current and new items
//	printd (DEBUG, "create matches hash");
			//do not free keys when destroying, they're not copies
			GHashTable *newlookup = g_hash_table_new_full
				(g_str_hash, g_str_equal, NULL, NULL);
			i = 0;
			for (member = entries; member != NULL; member = member->next)
			{
				g_hash_table_insert (newlookup, ((FileInfo *)member->data)->filename,
				 GINT_TO_POINTER (i));
				i++;
			}

			//this bit may separately apply to both views when they're the same
			i = 0;
			guint newindx = 0;
			GList *updates = NULL;
			FileInfo *currinfoptr, *newinfoptr;
			gpointer pindx, orig_key;
			GtkTreeIter iter;
			GtkTreeModel *mdl = GTK_TREE_MODEL (view->store);

//			printd (DEBUG, "check all current FileInfos");
			//walk current FileInfo's, deleting as we go, logging any that are changed
			//(there will always be at least one entry, "..")
			gtk_tree_model_get_iter_first (mdl, &iter);
			do
			{
loopstart:
				gtk_tree_model_get (mdl, &iter, FINFO, &currinfoptr, -1);
				if (g_hash_table_lookup_extended (newlookup, currinfoptr->filename,
					&orig_key, &pindx))
				{
					indx = (guint) pindx;
					newinfoptr = (FileInfo *) g_list_nth_data (entries, indx);
					//FIXME vfs may not have same data as local
					//FIXME make this faster
					if (newinfoptr->statbuf.st_atime != currinfoptr->statbuf.st_atime
					 ||	newinfoptr->statbuf.st_size != currinfoptr->statbuf.st_size
					 || newinfoptr->statbuf.st_mtime != currinfoptr->statbuf.st_mtime
					 || newinfoptr->statbuf.st_ctime != currinfoptr->statbuf.st_ctime
					 || newinfoptr->statbuf.st_mode != currinfoptr->statbuf.st_mode
					 || newinfoptr->statbuf.st_uid != currinfoptr->statbuf.st_uid
					 || newinfoptr->statbuf.st_gid != currinfoptr->statbuf.st_gid
					)
					{
						modes [indx].oldindx = i;
						modes [indx].oldmode = REFRESH_CHANGE;
						modes [indx].newindx = newindx++;
						updates = g_list_append (updates, newinfoptr);
					}
					else
					{
						//we don't want to use or add this one, later
						modes [indx].oldmode = REFRESH_KEEP;
						//so now get rid of data to prevent leakage
#ifdef USE_GLIB2_10
						g_slice_free1 (sizeof (FileInfo), newinfoptr);
//						DEALLOCATE (FileInfo, newinfoptr);
#else
						DEALLOCATE (FileInfo, newinfoptr);
#endif
					}
					i++;
				}
				else
				{	//current item is not hashed i.e. gone from dir
					gdk_threads_enter ();
					gboolean more = gtk_list_store_remove (view->store, &iter);
					gdk_threads_leave ();
					if (more)
						goto loopstart; //prevent double-iter-next, i unchanged
					else
						break;
				}
			} while (gtk_tree_model_iter_next (mdl, &iter));

			//tag any additions, and clean up redundant FileInfos to prevent leakage
			for (i = 0; i < itemcount; i++)
			{
				if (modes[i].oldmode == REFRESH_ADD)	//not previously detected = default (addition)
				{
					modes [i].newindx = newindx++;	//this will match the store index when created
					updates = g_list_append (updates, g_list_nth_data (entries, i));
				}
			}

			if (updates != NULL)
			{
//		printd (DEBUG, "create replacements store");
				//make store just with update information
				//(for each view because the indices are different)
				GtkListStore *newstore = e2_filelist_fill_store (updates, view);
				if (newstore != NULL)
				{
					//FIXME handle both views as appropriate
					GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store);
					//speedups ...
					//remember the current sorting arrangements
					gtk_tree_sortable_get_sort_column_id (sortable, &view->sort_column,
						&view->sort_order);
					//turn off model sorting before any rows are changed/added
					gtk_tree_sortable_set_sort_column_id (sortable,
						GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
//this kills the current selection, no evident way to prevent that
//					gtk_tree_view_set_model (GTK_TREE_VIEW (view->treeview), NULL);
					//walk modes array amending/appending relevant rows (maybe both views)
//			printd (DEBUG, "update current store");
					gchar *name, *size, *perm, *owner, *group, *mod, *access, *change, *key;
					GdkColor *foreground;
					for (i = 0; i < itemcount; i++)
					{
						if (modes[i].oldmode == REFRESH_CHANGE
							|| modes[i].oldmode == REFRESH_ADD)
						{
							gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (newstore),
								&iter, NULL, modes[i].newindx);
							gtk_tree_model_get (GTK_TREE_MODEL (newstore), &iter,
								FILENAME, &name,
								SIZE, &size,
								PERM, &perm,
								OWNER, &owner,
								GROUP, &group,
								MODIFIED, &mod,
								ACCESSED, &access,
								CHANGED, &change,
								NAMEKEY, &key,
								FINFO, &newinfoptr,
								FORECOLOR, &foreground,
								-1);
							if (modes[i].oldmode == REFRESH_CHANGE)
							{
								gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (view->store),
									&iter, NULL, modes[i].oldindx);
								gtk_tree_model_get (GTK_TREE_MODEL (view->store),
									&iter, FINFO, &currinfoptr, -1);
								//simply replace all changeable contents
								gdk_threads_enter ();
								gtk_list_store_set (view->store, &iter,
									SIZE, size,
									PERM, perm,
									OWNER, owner,
									GROUP, group,
									MODIFIED, mod,
									ACCESSED, access,
									CHANGED, change,
									FORECOLOR, foreground,	//maybe changed executable status
									-1);
								gdk_threads_leave ();
								//copy this so original can be cleared
								currinfoptr->statbuf = newinfoptr->statbuf;
							}
							else
							{	//append
								//the new FileInfo will be destroyed with its store
								//so get a copy for the main liststore
//								currinfoptr = ALLOCATE (FileInfo);
#ifdef USE_GLIB2_10
								currinfoptr = (FileInfo *) g_slice_alloc (sizeof (FileInfo));
								//currinfoptr = ALLOCATE (FileInfo);
#else
								currinfoptr = ALLOCATE (FileInfo);
#endif
								CHECKALLOCATEDWARN (currinfoptr, )
								if (currinfoptr != NULL)
								{
									*currinfoptr = *newinfoptr;
//gtk >= 2.10 can handle &iter = NULL
									gdk_threads_enter ();
									gtk_list_store_insert_with_values (view->store, &iter, -1,
										FILENAME, name,
										SIZE, size,
										PERM, perm,
										OWNER, owner,
										GROUP, group,
										MODIFIED, mod,
										ACCESSED, access,
										CHANGED, change,
										NAMEKEY, key,
										FINFO, currinfoptr,
										FORECOLOR, foreground,
										-1);
									gdk_threads_leave ();
								}
							}
							g_free (name);
							g_free (size);
							g_free (perm);
							g_free (owner);
							g_free (group);
							g_free (mod);
							g_free (access);
							g_free (change);
							g_free (key);
						}
					}
					gdk_threads_enter ();
					//re-sort the view using the current sort column & direction
					gtk_tree_sortable_set_sort_column_id (sortable, view->sort_column,
						view->sort_order);
					e2_fileview_refilter_list (view);
					gdk_threads_leave ();
//					g_idle_add ((GSourceFunc)_e2_fileview_run_hooks, view);
					//fixme do this when other view is processed
					gboolean newtimer = (app.used_stores == NULL);
			//FIXME not all entries member->data are cleared with newstore
					app.used_stores = g_slist_append (app.used_stores, newstore);
					if (newtimer)
					{
						printd (DEBUG, "setup to clear stores later");
						g_idle_add (e2_filelist_clear_old_stores, NULL);
					}
				}
				//no data free, the data are same as entries list data
				g_list_free (updates);	//the store is enough for other view ? FIXME idle ?
			}
			else	//updates = NULL
			{	//this is normally done in e2_fileview_refilter_list()
//				printd (DEBUG, "no need to update any item still in filelist");
				if (view->row > 0)
				{
					guint visible_rows = gtk_tree_model_iter_n_children (view->model, NULL);
					if (view->row > visible_rows - 1)
						view->row = visible_rows - 1;	//set to last line of list
					//if there was no selection, or the former selection is gone (after filter or deletion)
					//we want relative cursor-moves to stay in the same vicinity
					if (gtk_tree_selection_count_selected_rows (view->selection) == 0)
					{
						GtkTreePath *tpath = gtk_tree_path_new_from_indices (view->row, -1);
						gdk_threads_enter ();
						//this "marks" the item, but mere selection is not enough for this to work
						gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->treeview), tpath, NULL, FALSE);
						gtk_tree_selection_unselect_path (view->selection, tpath);
						gdk_threads_leave ();
						gtk_tree_path_free (tpath);
					}
				}
			}

			view->total_items = gtk_tree_model_iter_n_children
				(GTK_TREE_MODEL (view->store), NULL) - 1; //omit the ".." entry from the count

			g_hash_table_destroy (newlookup);	//CHECKME better at idle ?

#ifdef USE_GLIB2_10
			//can't use DEALLOCATE cuz > 1 item
			g_slice_free1 (sizeof (E2_RefreshInfo) * itemcount, modes);
#else
			g_free (modes);
#endif
		}

		F_FREE (local);
		//FIXME at idle?
		g_list_free (entries);
	}
	//else
	//FIXME warn user about error in reading data, with BGL closed

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, fileview_refresh list");
#endif
	//signal to the world ...
	LISTS_LOCK
	view->listcontrols.refresh_working = FALSE;
	LISTS_UNLOCK
	gdk_threads_enter ();
	e2_window_set_cursor (GDK_LEFT_PTR);
	gdk_threads_leave ();
	e2_filelist_enable_one_refresh (pnum);
//	printd (DEBUG, "finish refresh filelist for %s", view->dir);
	return GINT_TO_POINTER (1);	//normal exit, no more refresh
}
/*
#else //ndef E2_NEWREFRESH
/ * *
@brief reload list store for @a view and re-select relevant items

This records the names of currently-selected items in the view,
reloads the directory contents (which clears the old selection),
finds the rows in the new store that match the selected names,
and selects them.
If the directory associated with @a view is no longer accessible,
a suitable alternative is found and displayed.
Downstream functions expect gtk's BGL to be closed, hence this func does
too = gdk mutex management must be handled by the caller as appropriate.

@param view data structure for view being processed

@return
* /
void e2_fileview_refresh_list (ViewInfo *view)
{
	if (view->listing)
		return;	//the filelist is still being processed from last time
	printd (DEBUG, "start refresh filelist for %s", view->dir);

	gchar *utf = g_strdup (view->dir);	//copy, so that view->dir not clobbered
#ifdef E2_VFSTMP
	//CHECKME confirm valid path func works for v-dirs
	//FIXME goto dir when not mounted local
#endif
	if (e2_fs_get_valid_path (&utf, TRUE E2_ERR_NONE()))
		g_free (utf);
	else
	{	//desired path not accessible, go to somewhere that is ..
		gchar *msg = g_strdup_printf ("Cannot access %s, going to %s instead",
		view->dir, utf);
#ifdef E2_VFSTMP
		//FIXME cater for space-change message
#endif
		e2_output_print_error (msg, TRUE);
#ifdef E2_VFSTMP
		//FIXME if needed
		PlaceInfo *spacedata = ??;
		e2_pane_change_space_pointer (rt, spacedata);
#endif
		E2_PaneRuntime *rt = (view == curr_view ) ? curr_pane : other_pane;
		e2_pane_change_dir (rt, utf);
		g_free (utf);
		return;
	}

	if (view->selected_names != NULL)
	{
		//setup to get rid of any old hash
		g_idle_add_full (G_PRIORITY_LOW,
			(GSourceFunc) e2_fileview_treehash_free, view->selected_names, NULL);
	}
	//log the names of selected items in this view
//	printd (DEBUG, "log any selected items");
	view->selected_names = e2_fileview_log_selected_names (view);

	//clear and refill the store
	//FIXME support incremental updates
//	printd (DEBUG, "replace list content");
	view->refreshtype = E2_REFRESH;
	if (!e2_fileview_prepare_list (mounted, view))
		return;

	GtkTreeModel *mdl = gtk_tree_view_get_model (GTK_TREE_VIEW (view->treeview));
	GtkTreeIter iter;
	if (gtk_tree_model_get_iter_first (mdl, &iter))
	{
		//restore scroll positions
		/ * Not clear why this works, but ....
		the adjustments' values are unchanged, but the sw
		doesn't know about them unless signals are issued * /
		GtkAdjustment *adj;
		adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (view->treeview));
		gtk_adjustment_value_changed (adj);
//		adj = gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (view->treeview));
//		gtk_adjustment_value_changed (adj);
	}
	if (view->selected_names != NULL)
	{
//	printd (DEBUG, "reselect items");
//		clear out any old selection
//		gtk_tree_selection_unselect_all (view->selection);
		//try to reselect items, then at least clean the hash
		e2_fileview_reselect_names (view);
//	printd (DEBUG, "finish reselection for %s", view->dir);
	}

	printd (DEBUG, "finish refresh filelist for %s", view->dir);
}
#endif //ndef E2_NEWREFRESH
*/
/**
@brief create filelist for the pane related to @a view

This creates the backend list store, and related scrolled window
containing a treeview

@param view runtime data for view being processed

@return a scrolled window widget
*/
GtkWidget *e2_fileview_create_list (ViewInfo *view)
{
	//create liststore framework for the pane
	view->store = e2_filelist_make_store ();
	//setup column-order array
	gint array_row = (view == app.pane1.view) ? 0 : 1;
	e2_fileview_translate_cols_array (stored_col_order[array_row],
		displayed_col_order[array_row], MAX_COLUMNS);
	gint *width_array = col_width_store[array_row];
	gint *order_array = displayed_col_order[array_row];
	//element of cache-name strings
	gchar *panename = (view == app.pane1.view) ?
		app.pane1.name : app.pane2.name;

	/*now create tree view
	column order is determined by array displayed_col_order[].
	columns are named "0" to "7" for later searching */
	gint i, m;
	gchar *option_name;
	E2_OptionSet *set;
	gboolean show_this;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;
	GtkTreeSelection *sel;
	gchar *fontstr = (e2_option_bool_get ("custom-list-font")) ?
		e2_option_str_get ("list-font") : NULL;	//NULL will cause default font
	static gchar id[] = {'0', '\0'};  //for column header titles, used to check which column

	//unfiltered model needed for getting the sortable
	GtkTreeModel *mdl = GTK_TREE_MODEL (view->store);
	view->model = gtk_tree_model_filter_new (mdl, NULL);
	GtkWidget *tvw = gtk_tree_view_new_with_model (view->model);
	g_object_unref (G_OBJECT (view->store));	//kill the ref from model creation
	g_object_unref (G_OBJECT (view->model));	//kill the ref from treeview creation
	view->filtered_before = FALSE;	//start with fresh filters
	//save it for others to use
	view->treeview = tvw;

	GtkTreeSortable *sortable = GTK_TREE_SORTABLE (view->store);
	//allow unsorted display using GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID
//	gtk_tree_sortable_set_default_sort_func (sortable, NULL, NULL, NULL);

	//set general treeview properties
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tvw), TRUE);
	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tvw),	//useless with managed backcolors
		e2_option_bool_get ("panes-hinted"));
//#ifdef USE_GTK2_10
//FIXME use this if it's as functional as E2_ALTLEFTMOUSE, seems not so
//	gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (tvw), TRUE);	//"drag-selection"
//#endif
	//cache the view's selection pointer CHECKME maybe a problem with filtermodel
	view->selection = sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tvw));
	gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
#ifdef E2_SELTXT_RECOLOR
	if (e2_option_bool_get ("selectedtext-colorchange"))
		gtk_tree_selection_set_select_function (sel,
			(void *) _e2_fileview_selection_func, view, NULL);
#endif
/*	//when both panes are "same", make the slave-pane columns un-draggable
	gboolean fixed_order = (
		(e2_option_bool_get ("pane1-uses-other") && view == app.pane1.view)
		||
		(e2_option_bool_get ("pane2-uses-other") && view == app.pane2.view) );
*/
	//set columns' properties
	for (i = 0; i < MAX_COLUMNS; i++)
	{
		m = order_array[i];  //get the model/'external' index
		gtk_tree_sortable_set_sort_func (sortable, m,
			e2_all_columns[m].sort_func, &view->sort_order, NULL);

		//get the column visibility
		option_name = g_strdup_printf ("%s-show-column%d", panename, m);
		// e2_option_bool_get  defaults to FALSE
		set = e2_option_get (option_name);
		show_this = (set != NULL) ? e2_option_bool_get_direct (set) : TRUE;
		g_free (option_name);

		//create header widget, to support styling and avoid gtk bug with arrows
		GtkWidget *headerbox = gtk_hbox_new (FALSE, 1);
		GtkWidget *label = gtk_label_new (gettext(e2_all_columns[m].title)); //same title for each pane
		//expand = TRUE prevents col width < title width + sort indicator (if any)
		//spacing 0 when label expand = FALSE (or ese it crashes when col is too narrow to show all)
		gtk_box_pack_start (GTK_BOX(headerbox), label, FALSE, TRUE, 0);
		gtk_widget_show (label);
		if (m == FILENAME)
			//remember the Filename column label in case we need to use it for active-pane flagging
			view->name_label = GTK_LABEL (label);
		//set the current sort arrow
		GtkArrowType arrow;
		if (m == view->sort_column
				&& view->sort_order == GTK_SORT_DESCENDING)
			arrow = GTK_ARROW_UP;
		else
			arrow = GTK_ARROW_DOWN;
		view->sort_arrows[m] = gtk_arrow_new (arrow, GTK_SHADOW_IN);
		//this doesn't show the indicator at the end of the header ! (and different exp, fill don't help)
		gtk_box_pack_end (GTK_BOX(headerbox), view->sort_arrows[m], FALSE, FALSE, 0);
		gtk_widget_show (headerbox);

		renderer = gtk_cell_renderer_text_new ();
		g_object_set (G_OBJECT (renderer),
			"yalign", 0.0,
			"ypad", 0, //supposedly default, but this makes the rows closer
			"font", fontstr,	//this property not stored in model, as it doesn't change on the fly ...
			NULL);
//		gtk_cell_renderer_text_set_fixed_height_from_font
//			(GTK_CELL_RENDERER_TEXT (renderer), 1);
		//right-align the size column only
		if (m == SIZE)
			g_object_set (G_OBJECT (renderer),
				"xalign", 1.0,
				"xpad", 0,
				NULL);

  		id[0] = m+'0';
		column = gtk_tree_view_column_new_with_attributes (
			id, //this is for seaching & sorting, displayed title is in widget label
			renderer,
			"text", m,
			"foreground-gdk", FORECOLOR,
			"cell-background-gdk", BACKCOLOR,	//this column is generally NULLed, except for drop-target highlighting
			NULL);

		gint thiswidth = width_array[m];
		//doesn't like 0 widths set by gtk when a col is hidden
		if (thiswidth == 0)
			thiswidth = e2_all_columns[m].size;

		g_object_set (column,
			"sizing", GTK_TREE_VIEW_COLUMN_FIXED,
			"resizable", TRUE,
			"fixed-width", thiswidth,   //width_array[m],
			"visible", show_this,
			"clickable", TRUE,
//			"reorderable", !fixed_order,
			"reorderable", TRUE,
			"widget", headerbox,
			NULL);

		gtk_tree_view_append_column (GTK_TREE_VIEW (tvw), column);

#ifdef EDIT_INPLACE
		if (m == FILENAME)
		{	//setup for in-cell name-changing
			g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
			g_signal_connect (G_OBJECT (renderer), "edited",
				G_CALLBACK (_e2_fileview_name_edited_cb), view);
			g_signal_connect (G_OBJECT (renderer), "editing-started",
				G_CALLBACK (_e2_fileview_edit_start_cb), view);
			g_signal_connect (G_OBJECT (renderer), "editing-canceled",
				G_CALLBACK (_e2_fileview_edit_cancel_cb), view);
		}
#endif
		g_signal_connect (column, "clicked",
			G_CALLBACK (_e2_fileview_column_header_clicked_cb), view);

		//NB undocumented ->button
		//workaround for detecting header clicks to distinguish from drags
		g_signal_connect (column->button, "button-press-event",
			G_CALLBACK (_e2_fileview_column_header_buttonpress_cb), view);
		g_signal_connect (column->button, "button-release-event",
			G_CALLBACK (_e2_fileview_column_header_buttonrel_cb), view);
	}
	//setup inital sort parameters, ascending name col
//	view->sort_order = GTK_SORT_ASCENDING;
//	gtk_widget_show (view->sort_arrows[0]);
	//inital sort parameters set from cache
	gtk_tree_sortable_set_sort_column_id (sortable,
		view->sort_column, view->sort_order);
	gtk_widget_show (view->sort_arrows[view->sort_column]);

	//FIXME do this once, not for both panes
	button2updir = e2_option_bool_get ("button2-updir");

	g_signal_connect (tvw, "columns-changed",
	 G_CALLBACK(_e2_fileview_col_change_cb), view);

	//for E2_ALTLEFTMOUSE, double-clicks are handled in the click callback
	//BUT gtk issues this signal when Enter is pressed during a type-ahead
	//search, which we want
	g_signal_connect (tvw, "row-activated",
	 G_CALLBACK (_e2_fileview_row_activated_cb), view);

	//by default, type-ahead searching is enabled on column 0
	gtk_tree_view_set_search_equal_func	(GTK_TREE_VIEW (tvw),
		(GtkTreeViewSearchEqualFunc)_e2_fileview_match_filename, view, NULL);
	//DnD connections
//	gtk_drag_source_set (tvw, GDK_BUTTON1_MASK, target_table, n_targets,	//-1 if XDS is last,
//		GDK_ACTION_COPY); //can't use 2 of this fn, it seems
	gtk_drag_source_set (tvw, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
		target_table, n_targets,	//-1, //the last target (XDS) is supported for dest only
		GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
	gtk_drag_dest_set (tvw, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
		target_table, n_targets,
		GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);

	g_signal_connect (G_OBJECT (tvw), "drag-begin",
		G_CALLBACK (e2_dnd_drag_begin_cb), view);
	g_signal_connect (G_OBJECT (tvw), "drag-data-get",
		G_CALLBACK (e2_dnd_drag_data_get_cb), view);
	g_signal_connect (G_OBJECT (tvw), "drag-motion",
		G_CALLBACK (e2_dnd_drag_motion_cb), view);
	g_signal_connect (G_OBJECT (tvw), "drag-leave",
		G_CALLBACK (e2_dnd_drag_leave_cb), view);
//needed if GTK_DEST_DEFAULT_DROP not set, above
//	g_signal_connect (G_OBJECT (tvw), "drag-drop",
//		G_CALLBACK (e2_dnd_drag_drop_cb), view);

//	g_signal_connect (G_OBJECT (tvw), "drag-drop",
//		 G_CALLBACK(e2_dnd_drag_drop_cb), view);
	g_signal_connect (G_OBJECT (tvw), "drag-data-received",
		 G_CALLBACK(e2_dnd_drag_data_received_cb), view);
	//FIXME do these once only
//	atom_text_uri_list = gdk_atom_intern (target_table[0,0], FALSE);
//	atom_text_plain = gdk_atom_intern (target_table[1,0], FALSE);
//	atom_XdndDirectSave0 = gdk_atom_intern (target_table[2,0], FALSE);

//	g_signal_connect (G_OBJECT (tvw), "drag-data-delete",
//		G_CALLBACK (e2_dnd_drag_delete_cb), view);
	g_signal_connect (G_OBJECT (tvw), "button-press-event",
		G_CALLBACK (_e2_fileview_button_press_cb), view);
	g_signal_connect (G_OBJECT (tvw), "button-release-event",
		G_CALLBACK (_e2_fileview_button_release_cb), view);

	//pick up any key-bindings before the general key-press
	//BUT CHECKME order probably does not matter
	gchar *category = g_strconcat(_C(17),".",_C(32),NULL);
	e2_keybinding_register (category, tvw);  //_("general.panes
	g_free (category);

//	g_signal_connect (G_OBJECT (tvw), "popup-menu", handled by keypress cb now
//		G_CALLBACK (_e2_fileview_popup_cb), view);
	g_signal_connect (G_OBJECT (tvw), "key-press-event",
		G_CALLBACK (_e2_fileview_key_press_cb), view);

	g_signal_connect (G_OBJECT (tvw), "cursor-changed",
		G_CALLBACK (_e2_fileview_cursor_change_cb), view);

	GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
		GTK_SHADOW_ETCHED_IN);
	gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw),
		e2_option_int_get ("scrollbar-position"));
	gtk_container_add (GTK_CONTAINER (sw), tvw);

	gtk_widget_show (tvw);
	gtk_widget_show (sw);

	return sw;
}
