/* $Id: e2_window.c 2106 2010-05-30 23:42:15Z tpgww $

Copyright (C) 2004-2010 tooar <tooar@emelfm2.net>
Portions copyright (C) 1999 Michael Clark.

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, see http://www.gnu.org/licenses.
*/

/**
@file src/e2_window.c
@brief main window functions

main window functions, including actions on panes
*/
/**
\page status the status line

ToDo - description of how this works
*/

#include "emelfm2.h"
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <time.h>
#include "e2_window.h"
#include "e2_option.h"
#include "e2_action.h"
#include "e2_context_menu.h"
#include "e2_toolbar.h"
#include "e2_filelist.h"
#include "e2_task.h"
#ifdef E2_MOUSECUSTOM
# include "e2_mousebinding.h"
#endif

extern GList *cols_data;
extern gint col_width_store[2][MAX_COLUMNS];
extern gint stored_col_order[2][MAX_COLUMNS];
//main-window size before fullscreen enacted
gint real_width;
gint real_height;

static gdouble _e2_window_get_pos (GtkWidget *paned);

  /*****************/
 /***** utils *****/
/*****************/

/**
@brief create (but not show) paned widget to contain the file panes, and surrounding containers

Depending on whether the file panes are horiz or vert, a vert or horiz paned
widget is packed inside two boxes
The 'separator' between the panes is set to approximately its correct position
(this is before pane content is added, so that un-necessary content mapping is
minimized)

@param rt ptr to window data struct

@return
*/
static void _e2_window_create_pane_boxes (E2_WindowRuntime *rt)
{
	gint base;
	if (e2_option_bool_get ("panes-horizontal"))
	{
		rt->panes_horizontal = TRUE;
		rt->panes_outer_box = gtk_hbox_new (FALSE, 0);
		rt->panes_inner_box = gtk_vbox_new (FALSE, 0);
		rt->panes_paned = gtk_vpaned_new ();
		base =
#ifdef USE_GTK2_18
			app.main_alloc.height
#else
			app.main_window->allocation.height
#endif
			- 28;  //28 = notional height of commandbar
	}
	else
	{
		rt->panes_horizontal = FALSE;
		rt->panes_outer_box = gtk_vbox_new (FALSE, 0);
		rt->panes_inner_box = gtk_hbox_new (FALSE, 0);
		rt->panes_paned = gtk_hpaned_new ();
		base =
#ifdef USE_GTK2_18
			app.main_alloc.width
#else
			app.main_window->allocation.width
#endif
			- 5;  //5 = trial'n'error value to minimise separator jitter
	}
	if (base < 2)
		base = 2;
	//set interim panes sizes, to minimize un-necessary content mapping
	//real size set again, later
	gtk_paned_set_position (GTK_PANED (rt->panes_paned),
		base * rt->panes_paned_ratio);

	gtk_box_pack_start (GTK_BOX (rt->panes_inner_box), rt->panes_paned, TRUE, TRUE, 0);
	gtk_box_pack_start (GTK_BOX (rt->panes_outer_box), rt->panes_inner_box, TRUE, TRUE, 0);
}
/**
@brief get ratio value for position of paned widget divider

@param paned the widget for which the ratio is to be calculated

@return ratio: paned current position /  _paned->max_position
*/
static gdouble _e2_window_get_pos (GtkWidget *paned)
{
	GtkPaned *_paned = GTK_PANED (paned);
	gint pos = gtk_paned_get_position (_paned);
#ifdef USE_GTK2_99
   ->max_position
#endif
	return ((gdouble) pos / _paned->max_position);
}
/**
@brief change position of the divider for @a paned between filelists or lists and output

@a arg must be like one of: "X","*","X,Y","X,*" "*,X" or "*,*" where X and Y are
string representations of values in range [0.0..1.0] or may be just "0" or "1",
* represents any string containing that char
The first or only value sets the new position, a second value (if [0.01..0.99])
is logged as a 'previous' ratio
X,Y values < 0.01 are rounded down, values > 0.99 are rounded up
Expects BGL to be on/closed on arrival here
@param paned ptr to paned widget to be adjusted
@param pos pointer to store for paned position value
@param ratio_last pointer to store for the prior value of the 'paned ratio' (position / max. position)
@param arg action argument string

@return FALSE upon numerical conversion error or value outside of [0..1]
*/
static gboolean _e2_window_adjust_panes_ratio (GtkPaned *paned,
	gdouble *ratio_last, const gchar *arg)
{
	gfloat res, ratio_new, ratio_log;
	gchar *p1, *p2, *tail;
	gboolean retval, adjust_output;

	if (arg == NULL || *arg == '\0')
		return FALSE;

	retval = TRUE;
	adjust_output = (paned == GTK_PANED (app.window.output_paned));

	p1 = e2_utils_unquote_string (arg); //quotes may be there to protect '*'
	p2 = strchr (p1,','); //is there a pair of args ?
	if (p2 != NULL)
	{
		*p2 = '\0';
		p2++;
	}
	else
		p2 = p1;	//only 1
	if (strchr (p1, '*') != NULL)
		ratio_new = -1.0;
	else
	{
		g_strstrip (p1);
#ifdef __USE_GNU
		res = strtof (p1, &tail);
#else
		res = (gfloat) strtod (p1, &tail);
#endif
		if (*tail != '\0' //conversion error
			|| res < -0.0001 || res > 1.0001) //range error
		{
			retval = FALSE;
			ratio_new = 0.0;	//warning prevention
		}
		else
		{
			if (adjust_output) res = 1.0 - res;
			if (res < 0.01) res = 0.0;
			else if (res > 0.99) res = 1.0;
			ratio_new = res;
		}
	}
	if (p2 != p1)
	{
		if (strchr (p2, '*') != NULL)
			ratio_log = -1.0;
		else
		{
			g_strstrip (p2);
#ifdef __USE_GNU
			res = strtof (p2, &tail);
#else
			res = (gfloat) strtod (p2, &tail);
#endif
			if (*tail != '\0' //conversion error
			   || res < -0.0001 || res > 1.0001) //range error
			{
				retval = FALSE;
				ratio_log = 0.0;	//warning prevention
			}
			else
			{
				if (adjust_output) res = 1.0 - res;
				if (res < 0.01) res = 0.0;
				else if (res > 0.99) res = 1.0;
				ratio_log = res;
			}
		}
	}
	else
		ratio_log = ratio_new;

	g_free (p1);
	if (!retval)
		return retval;

	printd (DEBUG, "_e2_window_adjust_panes_ratio (paned:,ratio_last:%f3.2,ratio_new:%f3.2,ratio_log:%f3.2)",
		*ratio_last, ratio_new, ratio_log);

	if (p1 != p2 && (ratio_new < -0.0001 || ratio_log < -0.0001))
	{	// '*' in either or both parts of 2-part arg
		if (ratio_new >= -0.0001)
		{
#ifdef USE_GTK2_99
   ->max_position
#endif
			res = (gfloat)gtk_paned_get_position(paned)/paned->max_position;
			ratio_log = (gfloat)*ratio_last;
			if (ratio_log < (res - 0.0001) || ratio_log > (res + 0.0001)) //floats !=
				ratio_new = ratio_log;
		}
		else if (ratio_log >= -0.0001)
		{
#ifdef USE_GTK2_99
   ->max_position
#endif
			res = (gfloat)gtk_paned_get_position(paned)/paned->max_position;
			if (ratio_log < (res - 0.0001) || ratio_log > (res + 0.0001)) //floats !=
				ratio_new = ratio_log;
			ratio_log = (gfloat)*ratio_last;
		}
		else // '*' in both parts
		{
#ifdef USE_GTK2_99
   ->max_position
#endif
			ratio_new = ratio_log = paned->max_position * (gfloat)*ratio_last;
		}
	}

	if (ratio_new < -0.0001)
		ratio_new = *ratio_last;
	gtk_paned_set_position (paned, paned->max_position * ratio_new);
	//typically arg has 1 part, and ratio_log == ratio_new
	if (ratio_log > 0.01 && ratio_log < 0.99)
		*ratio_last = (gdouble)ratio_log;

	if (adjust_output) //adjusting output divider
	{
		if (ratio_new > 0.99 &&
#ifdef USE_GTK2_18
			gtk_widget_has_focus (GTK_WIDGET (curr_tab->text))
#else
			GTK_WIDGET_HAS_FOCUS (curr_tab->text)
#endif
		)
		{
			WAIT_FOR_EVENTS
			gtk_widget_grab_focus (app.pane1.view.treeview); //but this might also be hidden
		}
		else if (ratio_new < 0.01 &&
#ifdef USE_GTK2_18
			(gtk_widget_has_focus (app.pane2.view.treeview) ||
			 gtk_widget_has_focus (app.pane1.view.treeview)))
#else
			(GTK_WIDGET_HAS_FOCUS (app.pane2.view.treeview) ||
			 GTK_WIDGET_HAS_FOCUS (app.pane1.view.treeview)))
#endif
		{
			WAIT_FOR_EVENTS
			gtk_widget_grab_focus (GTK_WIDGET(curr_tab->text));
		}
	}
	else
	{	//do this only for filelist divider changes (may hang for output pane divider)
		if (ratio_new > 0.99 &&
#ifdef USE_GTK2_18
			gtk_widget_has_focus (app.pane2.view.treeview))
#else
			GTK_WIDGET_HAS_FOCUS (app.pane2.view.treeview))
#endif
		{
			WAIT_FOR_EVENTS	//note - expects BGL closed
			gtk_widget_grab_focus (app.pane1.view.treeview);
		}
		else if (ratio_new < 0.01 &&
#ifdef USE_GTK2_18
			gtk_widget_has_focus (app.pane1.view.treeview))
#else
			GTK_WIDGET_HAS_FOCUS (app.pane1.view.treeview))
#endif
		{
			WAIT_FOR_EVENTS
			gtk_widget_grab_focus (app.pane2.view.treeview);
		}
	}

	return TRUE;
}
/**
@brief change position of file-panes separator

This is a wrapper for _e2_window_adjust_panes_ratio ()
Downstream expects BGL closed
@param arg string with new ratio to be set

@return
*/
void e2_window_adjust_pane_ratio (const gchar *arg)
{
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_ratio_last, arg);
}
/**
@brief set default window icon list

Set default window icon list from files in configured icon_dir.
That dir is assumed native
Each with name starts with "emelfm2_", and has trailing 'xx.png' where xx are digits

@param base filename base of the icon files

@return
*/
static void _e2_window_set_icon (void)
{
	gchar *icons_dir = e2_utils_get_icons_path (FALSE);
	DIR *d = e2_fs_dir_open (icons_dir);
	if (d == NULL)
	{
		printd (WARN, "could not open window icon directory '%s' (%s)",
			icons_dir, g_strerror (errno));
		g_free (icons_dir);
		gtk_window_set_default_icon_name (BINNAME);
		return;
	}
	GList *list = NULL;
	const gchar *name;	//name is like "emelfm2_24.png"
	gchar *base = BINNAME"_";	//this is the 'prefix' for window icon filenames
	guint len = strlen (base) + 6;	//assuming ascii, +6 allows for xx.png, filters out the 'alternates'
	struct dirent entry;
	struct dirent *entryptr;
	while (TRUE)
	{
		if (e2_fs_dir_read (d, &entry, &entryptr) || entryptr == NULL)
				break;
		name = entry.d_name;
		if (g_str_has_prefix (name, base) && g_str_has_suffix (name, ".png") &&
			strlen (name) == len)
		{
			gchar *file = g_build_filename (icons_dir, name, NULL);
			GError *error = NULL;
			GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (file, &error);
			if (error != NULL)
			{
				printd (WARN, "could not open image file '%s' (%s)", file, error->message);
				g_free (file);
				g_error_free (error);
				continue;
			}
			g_free (file);
			list = g_list_append (list, pixbuf);
		}
	}
	if (list != NULL)
	{
		gtk_window_set_default_icon_list (list);
		//gtk copies list and ref's its items, so cleanup here
		g_list_foreach (list, (GFunc) g_object_unref, NULL);
		g_list_free (list);
	}
	g_free (icons_dir);
	e2_fs_dir_close (d);
}

  /*********************/
 /***** callbacks *****/
/*********************/
/**
@brief callback for "size-allocate" signal on app.pane1.outer_box
This happens when file panes divider moves (among other instances)
@param widget UNUSED the resized widget, app.pane1.outer_box
@param alloc the allocation for @a widget
@param rt pointer to window data struct

@return
*/
static void _e2_window_filepanes_adjusted_cb (GtkWidget *widget,
	GtkAllocation *alloc, E2_WindowRuntime *rt)
{
	static gint prev_size = -1;
	if ((rt->panes_horizontal && alloc->height != prev_size)
	 || (!rt->panes_horizontal && alloc->width != prev_size))
	{	//this is not just some gtk housekeeping
		printd (DEBUG, "_e2_window_filepanes_adjusted_cb (widget:,alloc:%d-%d,rt:_)", alloc->width, alloc->height);
		prev_size = (rt->panes_horizontal) ? alloc->height : alloc->width;
		rt->panes_paned_ratio = _e2_window_get_pos (rt->panes_paned);
//		printd (DEBUG, "1ratio: %f", rt->panes_paned_ratio);
		if (rt->panes_paned_ratio > 0.001 && rt->panes_paned_ratio < 0.999)
			rt->panes_paned_ratio_last = rt->panes_paned_ratio;
	}
}
/**
@brief after a file pane is mapped or unmapped, ensure the correct toggle button is visible in the other pane's toolbar

This fixes the toggle button when the panes divider is dragged

@param widget ptr to scrolled window for the pane that has just been mapped
@param state TRUE for map signal, FALSE for unmap

@return
*/
static void _e2_window_map_pane_cb (GtkWidget *widget, gpointer state)
{
	E2_ToggleType num = (widget == app.pane1.pane_sw) ?
		E2_TOGGLE_PANE2FULL : E2_TOGGLE_PANE1FULL;
	if (e2_toolbar_toggle_button_get_state (toggles_array [num])
			== GPOINTER_TO_INT (state))
		e2_toolbar_button_toggle (toggles_array [num]);
}
/**
@brief callback for signal emitted when output pane size is allocated
This happens when output content change varies the size of @a widget,
and also when the output-paned divider is moved
@param widget UNUSED the resized widget, app.outbook
@param alloc the allocation for @a widget
@param rt pointer to window data struct

@return TRUE, always
*/
static void _e2_window_outputpane_adjusted_cb (GtkWidget *widget,
	GtkAllocation *alloc, E2_WindowRuntime *rt)
{
	static gint prev_height = -1;
	if (alloc->height != prev_height)
	{	//this is not just more output being displayed
		printd (DEBUG, "_e2_window_outputpane_adjusted_cb (widget:,alloc:%d, %d, %d-%d,rt:_)", alloc->x, alloc->y, alloc->width, alloc->height);
		prev_height = alloc->height;
		rt->output_paned_ratio = _e2_window_get_pos (app.window.output_paned);
//		printd (DEBUG, "2ratio: %f", rt->output_paned_ratio);
		//remember visibility state
		app.output.visible = (rt->output_paned_ratio != 1.0);
		if (rt->output_paned_ratio > 0.001 && rt->output_paned_ratio < 0.999)
			rt->output_paned_ratio_last = rt->output_paned_ratio;
		//make sure the correct toggle btn is shown
		gboolean truenow =
		e2_toolbar_toggle_button_get_state (toggles_array [E2_TOGGLE_OUTPUTFULL]);
		if ((!truenow && rt->output_paned_ratio < 0.001)
		  ||(truenow && rt->output_paned_ratio >= 0.001))
			e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTFULL]);
	}
}
/**
@brief after the output pane is mapped or unmapped, ensure the correct toggle button is visible in the commandbar

This fixes the toggle button when the panes divider is dragged

@param widget ptr to output pane scrolled window that has just been mapped
@param state TRUE when mapped, FALSE when unmapped

@return
*/
static void _e2_window_map_output_cb (GtkWidget *widget, gpointer state)
{
//	printd (DEBUG, "_e2_window_map_output_cb (widget:,state:%s)", state ? "true" : "false");
	//use toolbar runtime and its .option because that string is not translated
	//make sure the toggle for the other pane is not 'split' form
	if (e2_toolbar_toggle_button_get_state (toggles_array [E2_TOGGLE_OUTPUTSHADE])
		== GPOINTER_TO_INT (state))
		e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTSHADE]);
}
/**
@brief show output pane, at its previous size

This is a callback for ouptput pane "focus-in-event",
and also used generally
Runs output adjust ratio action
Downstream expects BGL to be on/closed

@param widget UNUSED the newly-focused entry widget or NULL
@param data UNUSED

@return FALSE to propagate the event to other handlers
*/
gboolean e2_window_output_show (GtkWidget *widget, gpointer data)
{
	if (!app.output.visible)
		_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
			&app.window.output_paned_ratio_last, "*");

	return FALSE;
}
/**
@brief hide output pane

This is a callback for command-line "focus-out-event",
and also used generally
Runs output adjust ratio action
Downstream expects BGL to be on/closed

@param widget UNUSED newly-departed widget or NULL
@param event UNUSED pointer to event data struct
@param user_data UNUSED data specified when callback was connected

@return FALSE to propagate the event to other handlers
*/
gboolean e2_window_output_hide (GtkWidget *widget, GdkEventFocus *event,
	gpointer user_data)
{
	if (app.output.visible)
		_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
			&app.window.output_paned_ratio_last, "0");

	gtk_widget_grab_focus (curr_view->treeview);
	return FALSE;
}
/**
@brief callback for "window-state-event" signal emitted when the main window changes
@param widget the main-window object
@param event the GdkEventWindowState which triggered this signal
@param user_data UNUSED data specified when the signal handler was connected

@return FALSE always, so event propogates
*/
static gboolean _e2_window_change_cb (GtkWidget *widget, GdkEventWindowState *event,
	gpointer user_data)
{
	//GdkWindowState event->changed_mask;
	//GdkWindowState event->new_window_state
	if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
	{
		//toggling fullscreen on or off will grab the "unfull" size each time
#ifdef USE_GTK2_18
		GtkAllocation alloc;
		gtk_widget_get_allocation (app.main_window, &alloc);
		real_width = alloc.width;
		real_height = alloc.height;
#else
		real_width = app.main_window->allocation.width;
		real_height = app.main_window->allocation.height;
#endif
		printd (DEBUG, "window-change cb main window size: W %d H %d", real_width, real_height);
	}
	app.mainwindow_state = event->new_window_state;
	return FALSE;
}
/**
@brief callback for signal emitted when the main window is first shown

paned dividers' positions are 'crudely' set when those widgets are created,
but now all things are setup properly, the sizes are fine-tuned, here
Pane resize etc callbacks are then connected, so that we don't get unnecessary
callbacks during session setup
After all is stable, the toolbars space handling arrangements are initialized,
again, to avoid unnecessary callbacks

@param window UNUSED, the window widget being shown
@param rt pointer to window data struct

@return TRUE, always
*/
static void _e2_window_show_cb (GtkWidget *window, E2_WindowRuntime *rt)
{
	printd (DEBUG, "show main window cb");
	//initialize window flags
	app.mainwindow_state = gdk_window_get_state (
#ifdef USE_GTK2_14
		gtk_widget_get_window (app.main_window));
#else
		app.main_window->window);
#endif
	//notice future window-state changes
	g_signal_connect (G_OBJECT(app.main_window), "window-state-event",
		G_CALLBACK(_e2_window_change_cb), NULL);
#ifdef USE_GTK2_99
   ->max_position
#endif
	gtk_paned_set_position (GTK_PANED (rt->panes_paned),
		GTK_PANED (rt->panes_paned)->max_position * rt->panes_paned_ratio);
	gtk_paned_set_position (GTK_PANED (app.window.output_paned),
		GTK_PANED (app.window.output_paned)->max_position * rt->output_paned_ratio);
	//ensure inter-list divider-position is logged when divider is moved
	g_signal_connect (G_OBJECT (app.pane1.outer_box), "size-allocate",
			G_CALLBACK (_e2_window_filepanes_adjusted_cb), rt);
	//ensure correct toggle button is displayed when a pane divider is dragged
	g_signal_connect (G_OBJECT (app.pane1.pane_sw), "map",
			G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (TRUE));
	g_signal_connect (G_OBJECT (app.pane1.pane_sw), "unmap",
			G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (FALSE));
	g_signal_connect (G_OBJECT (app.pane2.pane_sw), "map",
			G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (TRUE));
	g_signal_connect (G_OBJECT (app.pane2.pane_sw), "unmap",
			G_CALLBACK (_e2_window_map_pane_cb), GINT_TO_POINTER (FALSE));
	//ensure inter-pane divider-position is logged when divider is moved
	g_signal_connect (G_OBJECT (app.outbook), "size-allocate",
			G_CALLBACK (_e2_window_outputpane_adjusted_cb), rt);
	g_signal_connect (G_OBJECT (app.outbook), "map",
			G_CALLBACK (_e2_window_map_output_cb), GINT_TO_POINTER (TRUE));
	g_signal_connect (G_OBJECT (app.outbook), "unmap",
			G_CALLBACK (_e2_window_map_output_cb), GINT_TO_POINTER (FALSE));

	gtk_notebook_set_current_page (GTK_NOTEBOOK (app.outbook), 0);

	WAIT_FOR_EVENTS; //faster window completion if we hide buttons now, if
					// needed (i.e. dont wait till main loop)
	//now that window is established, we are ready to initialise all toolbars' overflow handling
	e2_toolbar_initialise_space_handler (NULL);
}
/**
@brief callback for main window "destroy-event", "delete-event" signals

@param widget UNUSED the window button which was activated
@param event UNUSED pointer to event data
@param forced pointerised gboolean, NULL if the user is permitted to choose to keep the window open

@return TRUE if the user cancelled the close, or else it does not return at all
*/
static gboolean _e2_window_gone_cb (GtkWidget *widget, GdkEvent *event, gpointer forced)
{
	e2_main_closedown (GPOINTER_TO_INT (forced), TRUE, TRUE);
	return TRUE;
}

  /*******************/
 /***** actions *****/
/*******************/
/**
@brief toggle between horizontal and vertical file panes

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
static gboolean _e2_window_toggle_panes_direction
	(gpointer from, E2_ActionRuntime *art)
{
	e2_option_bool_toggle ("panes-horizontal");
	e2_window_recreate (&app.window);	//FIXME Q this
	return TRUE;
}
/**
@brief change the position of the filepanes separator

This is a wrapper for _e2_window_adjust_panes_ratio ()
Downstream expects BGL to be on/closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_adjust_pane_ratio_action
	(gpointer from, E2_ActionRuntime *art)
{
	return (_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_ratio_last, (const gchar *) art->data));
}
/**
@brief respond to pane 1 full-width toggle button

The visible toggle button is swapped, and the panes-ajust func is called with
either "1" or "*"
Expects BGL closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_toggle_full_pane1 (gpointer from, E2_ActionRuntime *art)
{
	const gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE1FULL])) ?
		"1" : "*";	//no translation
	return (_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_ratio_last, arg));
}
/**
@brief respond to pane2 full-width toggle button

The visible toggle button is swapped, and the panes-ajust func is called with
either "0" or "*"
Expects BGL to be on/closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_toggle_full_pane2 (gpointer from, E2_ActionRuntime *art)
{
	const gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE2FULL])) ?
		"0" : "*";	//no translation
	return (_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_ratio_last, arg));
}
/**
@brief respond to output pane full-window toggle button

The visible toggle button is swapped, and the panes-adjust func is called with
either "1" or "*"
Expects BGL to be on/closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_toggle_full_output (gpointer from, E2_ActionRuntime *art)
{
	gboolean next_state =
	!e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTFULL]);

	if (next_state)
		e2_output_get_bottom_iter (&app.tab);	//set mark for current bottom position

	gboolean retval =
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
		&app.window.output_paned_ratio_last, (next_state) ? "*":"1");

	if (retval && next_state)
	{
		//cancelling full-window reverts to "last" split ratio, never to hidden
		//if appropriate, adjust scroll position to show the stuff at the bottom of the former view
		e2_output_scroll_to_bottom (&app.tab);	//get back to the bottom portion
/*		E2_OutputTabRuntime *trt = &app.tab;
		GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment
			(GTK_SCROLLED_WINDOW (trt->scroll));
		gdouble value = gtk_adjustment_get_value (vadj);
		if (value >= (vadj->upper - vadj->page_size))
		{	//we're showing the end of the text
			value += vadj->page_increment; //CHECKME find text actually at top of pane ?
			gtk_adjustment_set_value (vadj, value);
		}
*/
		//make sure the other toggle btn is correct
		if (app.window.output_paned_ratio_last < 0.999)
			e2_toolbar_toggle_button_set_state
				(toggles_array [E2_TOGGLE_OUTPUTSHADE], FALSE);
	}
	return retval;
}
/**
@brief respond to output pane visible toggle button

The visible toggle button is swapped, and the panes-ajust fn
is called with either "1" or "*"
Expects BGL to be on/closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_toggle_visible_output (gpointer from, E2_ActionRuntime *art)
{
	const gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTSHADE])) ?
		"0" : "*";	//no translation
	return (_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
		&app.window.output_paned_ratio_last, arg));
}
/**
@brief implement adjust-output-ratio action

This is a wrapper for _e2_window_toggle etc
The visible toggle button is swapped, and the panes-ajust fn
is called with "*" ",1" or ",0"
Expects BGL to be on/closed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the change was done
*/
static gboolean _e2_window_adjust_output_ratio_action
	(gpointer from, E2_ActionRuntime *art)
{
	const gchar *s, *t, *arg = (const gchar *) art->data;
	//'*' in first or only part of arg ...
	gboolean star = ((s = strchr(arg,'*')) != NULL &&
					((t = strchr(arg,',')) == NULL || t > s));
	gboolean shade = (star && app.window.output_paned_ratio < 0.01);
	if (shade)
		e2_output_get_bottom_iter (&app.tab);	//set mark for bottom position

	gboolean retval =
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
		&app.window.output_paned_ratio_last, arg);

	if (retval && shade)
		e2_output_scroll_to_bottom (&app.tab);	//get back to the bottom portion of the former view

	return retval;
}
/**
@brief window-fullscreen toggle action
This can apply to any application window
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if @a from has atop-level window
*/
static gboolean _e2_window_toggle_full_screen (gpointer from, E2_ActionRuntime *art)
{
	GtkWidget *top = gtk_widget_get_toplevel (from);
#ifdef USE_GTK2_18
	if (gtk_widget_is_toplevel (top))
#else
	if (GTK_WIDGET_TOPLEVEL (top))
#endif
	{
		GdkWindow *win =
#ifdef USE_GTK2_14
			gtk_widget_get_window (top);
#else
			top->window;
#endif
		GdkWindowState state = gdk_window_get_state (win);
		if (state & GDK_WINDOW_STATE_FULLSCREEN)
			gdk_window_unfullscreen (win);
		else
			gdk_window_fullscreen (win);
		return TRUE;
	}
	return FALSE;
}

  /******************/
 /***** public *****/
/******************/

#ifdef E2_COMPOSIT
/**
@brief set or reset translucency of top-level window @a window
Levels < 10 are treated as 0 and > 95 is ignored
Expects BGL on/active
@param window the widget for the window to be processed
@param level opacity %, 50 (faint) to 100 (opaque), or -1 (config default level)

@return
*/
void e2_window_set_opacity (GtkWidget *window, gint level)
{
/*	GdkScreen *screen;
#ifndef USE_GTK2_10
	GdkColormap *colormap;
#endif
*/
	gint deflevel = e2_option_int_get ("window-opacity");	//50=faint...100=opaque
	if (deflevel < 100)	//compositing is active
	{
		if (level < 0)
			level = deflevel;
		//non-top-level windows more opaque than the default setting
		GtkWindowType wtype;
		g_object_get (G_OBJECT (window), "type", &wtype, NULL);
		if (wtype != GTK_WINDOW_TOPLEVEL)
			level = MAX (deflevel + 5, level);
		if (level < 30)	//ignore unusably-invisible levels
			level = 30;
		else if (level > 96)	//ignore nearly-opaque or > 100 levels
			level = 100;

#ifdef USE_GTK2_12
		gtk_window_set_opacity (GTK_WINDOW (window), (gdouble) level / 100.0);
#endif

/* FIXME
		//turn off translucence if it was on, or turn on or adjust translucence
#ifdef USE_GTK2_12
		if (gtk_widget_is_composited (window))
#elif defined (USE_GTK2_10)
		screen = gtk_widget_get_screen (window);
		if (gdk_screen_is_composited (screen))
#else
		screen = gtk_widget_get_screen (window);
		colormap = gdk_screen_get_rgba_colormap (screen);
		if (colormap != NULL)	//not entirely sufficient
#endif
			g_signal_connect (G_OBJECT (window), "screen-changed",
				G_CALLBACK (_e2_window_screen_changed_cb), GINT_TO_POINTER (level));
		else
			g_signal_handlers_disconnect_matched (G_OBJECT (window),
				G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_window_screen_changed_cb, NULL);
#ifdef USE_GTK2_12
		screen = gtk_widget_get_screen (window);
#endif
		//FIXME with same screen, this does not change settings!
		//AND NOT CORRECT, even if it did change settings
		_e2_window_screen_changed_cb (window, screen, GINT_TO_POINTER (level));
	}
	else	//compositing option not in effect
	{
		//check for tranlucent on now, turn it off if so
#ifdef USE_GTK2_12
		if (!gtk_widget_is_composited (window))
#elif defined (USE_GTK2_10)
		screen = gtk_widget_get_screen (window);
		if (!gdk_screen_is_composited (screen))
#else
		screen = gtk_widget_get_screen (window);
		colormap = gdk_screen_get_rgba_colormap (screen);
		if (colormap == NULL)	//not entirely sufficient
#endif
		{
			//turn off translucence
			g_signal_handlers_disconnect_matched (G_OBJECT (window),
				G_SIGNAL_MATCH_FUNC, 0, 0, NULL, _e2_window_screen_changed_cb, NULL);
#ifdef USE_GTK2_12
			screen = gtk_widget_get_screen (window);
#endif
			_e2_window_screen_changed_cb (window, screen, GINT_TO_POINTER (100));
		}
*/
	}
}
#endif
/**
@brief setup custom window title
This probably needs BGL to be closed upon arrival
@param wid dialog or window widget
@param title custom string to append to window titlebar, or NULL
@return
*/
void e2_window_set_title (GtkWidget *wid, const gchar *title)
{
	if (title != NULL)
	{
		gchar *tt = e2_utils_strcat (PROGNAME ": ", title);
		gtk_window_set_title (GTK_WINDOW (wid), tt);	//needs BGL ?
		g_free (tt);
	}
	else
		gtk_window_set_title (GTK_WINDOW (wid), PROGNAME);
}
/**
@brief set app window cursor to @a type
Expects BGL on/active
@param type enumerator of desired cursor type

@return
*/
void e2_window_set_cursor (GdkCursorType type)
{
	GdkCursor *cursor = gdk_cursor_new (type);
	gdk_window_set_cursor (
#ifdef USE_GTK2_14
		gtk_widget_get_window (app.main_window),
#else
		app.main_window->window,
#endif
		cursor);
	gdk_cursor_unref (cursor);
	gdk_flush ();
}
/* *
@brief set output pane size  UNUSED

for internal use, not a toolbar toggle
Downstream expects BGL to be on/closed

@param rt window rt data structure
@param arg string indicating the desired state of the pane e.g "*" or "0"

@return
*/
/* void e2_window_adjust_output_pane_ratio (E2_WindowRuntime *rt, const gchar *arg)
{
	_e2_window_adjust_panes_ratio (GTK_PANED (rt->output_paned),
		&rt->output_paned_ratio_last, arg);
}*/

guint last_selected_rows = -1;	//-1 ensures it always reports at session start
static guint last_total_rows;

/**
@brief update status line message about selected and total items counts

This is called periodically by the status line timer function, when that's
not suspended
Any ".." entry is filtered out from the count
Expects BGL off/open

@param userdata UNUSED

@return TRUE, always (so the timer is never cancelled)
*/
gboolean e2_window_update_status_bar (gpointer userdata)
{
	//CHECKME make this happen after specific events (refresh, refilter, cd, swap panes)
	//not on a timer
#ifdef E2_STATUS_BLOCK
	if (g_atomic_int_get (&app.status_working))
	{
		printd (DEBUG, "update status bar BLOCKED");
		return TRUE;
	}
#endif
//	printd (DEBUG, "e2_window_update_status_bar");
#ifdef E2_STATUS_BLOCK
	g_atomic_int_set (&app.status_working, 1);
#endif
	gint selected_rows, total_rows;
	GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (curr_view->treeview));
	GtkTreeSelection *sel = curr_view->selection;

	selected_rows = gtk_tree_selection_count_selected_rows (sel);
	total_rows = gtk_tree_model_iter_n_children (model, NULL);
	if ((selected_rows != last_selected_rows) || (total_rows != last_total_rows))
	{
		if (e2_option_bool_get ("show-updir-entry"))
		{	//make status-line counts ignore the updir entry
			//the treeview sorting functions always put any "../" entry at the
			//start of the list, so if such entry is selected, it will be the 1st
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter_first (model, &iter))
			{ //really, there will always be an iter
				total_rows--;
				if (gtk_tree_selection_iter_is_selected (sel, &iter))
					selected_rows--;
			}
		}
		last_selected_rows = selected_rows;
		last_total_rows = total_rows;
		gchar *status_text1 = (curr_view->total_items > total_rows) ?
			g_strdup_printf (_("displayed & %d concealed "), curr_view->total_items-total_rows) :
			"" ;
		gchar *status_text2 = g_strdup_printf (
			_("%s%d selected item(s) of %d %sin %s"), ":::  ",
#ifdef E2_VFSTMP
	//FIXME tip when not mounted local
#else
			selected_rows, total_rows, status_text1, curr_view->dir);
#endif

		//generally remove trailing / (ascii)
#ifdef E2_VFSTMP
	//FIXME tip when not mounted local
#else
		if (*(curr_view->dir + sizeof (gchar)) != '\0')	//dir is a single char when at root dir
#endif
		{
			gint len = strlen (status_text2);
			*(status_text2 + len - sizeof (gchar)) = '\0';
		}
		gdk_threads_enter ();
		gtk_label_set_text (GTK_LABEL (app.status_bar_label2), status_text2);
		gdk_threads_leave ();
		if (*status_text1 != '\0')
			g_free (status_text1);
		g_free (status_text2);
	}
#ifdef E2_STATUS_BLOCK
	g_atomic_int_set (&app.status_working, 0);
#endif
//	printd (DEBUG, "e2_window_update_status_bar ends");
	return TRUE;  //never turn this off
}
#ifdef E2_STATUS_REF
gint statref_count = 0;
#endif
gint lastinterval = E2_STATUSREPORT_INTERVAL;
#ifndef E2_STATUS_DEMAND
/**
@brief turn on status bar 'selected files' message refreshing

@param interval no of milliseconds between updates, or -1 for default

@return
*/
void e2_window_enable_status_update (gint interval)
{
	if (interval < 0)
		interval = lastinterval;
	else
		lastinterval = interval;
#ifdef E2_STATUS_REF
	statref_count--;
	if (statref_count == 0)
	{
#endif
#ifdef E2_STATUS_BLOCK
		g_atomic_int_set (&app.status_working, 0);
#endif
		app.timers[STATUS_T] = g_timeout_add (interval, e2_window_update_status_bar, NULL);
#ifdef E2_STATUS_REF
	}
	else if (statref_count < 0)
		statref_count = 0;
#endif
}
/**
@brief turn off status bar 'selected files' message refreshing

@return
*/
void e2_window_disable_status_update (void)
{
#ifdef E2_STATUS_REF
	statref_count++;
	if (statref_count == 1)
	{
//		printd (BEBUG, "block update status bar cb");
#endif
		if (app.timers[STATUS_T] > 0)
		{
			g_source_remove (app.timers[STATUS_T]);
			app.timers[STATUS_T] = 0;
#ifdef E2_STATUS_REF
		}
#endif
	}
}
#endif	//ndef E2_STATUS_DEMAND
/* *
@brief display status bar custom message

The specified string is added to the end of the hbox, but only displayed
if it is the first message in the box
This allows multiple messages to be queued
FIXME the messages may be cancelled in any order if threads are in force ...

@param message string to be displayed, may have pango markup

@return
*/
/* UNUSED
void e2_window_show_status_message (gchar *message)
{
	GtkWidget *label = gtk_label_new (NULL);
	gtk_label_set_markup (GTK_LABEL (label), message);
	gtk_box_pack_start (GTK_BOX (app.status_bar_box3), label, FALSE, FALSE, 0);
	GList *children = GTK_BOX (app.status_bar_box3)->children;
	if (g_list_length (children) == 1)
		gtk_widget_show_all (app.status_bar_box3);
} */
/* *
@brief cancel display of status bar custom message

@return
*/
//FIXME the logic here is crap - needs to be thread-safe
/* UNUSED
void e2_window_remove_status_message (void)
{
	GList *children = GTK_BOX (app.status_bar_box3)->children;
	gint num = g_list_length (children);
	if (num > 1)
	{	//show the next label in the queue (stupid !!)
		GtkWidget *label = ((GtkBoxChild *) children->next->data)->widget;
		gtk_widget_show_all (label);
	}
	if (num ==1)
		gtk_widget_hide (app.status_bar_box3);
	if (num > 0)
	{
		GtkWidget *label = ((GtkBoxChild *) children->data)->widget;
		gtk_container_remove (GTK_CONTAINER (app.status_bar_box3), label);
	}
} */
/**
@brief show statusline message @a labeltext
Any existing message is simply replaced
Gtk's BGL is expected to be open/off
@param message UTF-8 string, which may include pango markup

@return
*/
void e2_window_show_status_message (gchar *message)
{
	GList *children = gtk_container_get_children (GTK_CONTAINER (app.status_bar_box3));
	if (children != NULL)
	{
		gdk_threads_enter ();	//maybe the message is already displayed
		if (GTK_IS_WIDGET (children->data))
			gtk_widget_destroy (children->data);	//FIXME make this a friendly Q
		gdk_threads_leave ();
		g_list_free (children);
	}
//	GtkWidget *label = gtk_label_new (message);
	GtkWidget *label = gtk_label_new (NULL);
	gchar *public = g_markup_escape_text (message, -1);
	gtk_label_set_markup (GTK_LABEL (label), public);
	g_free (public);
	gdk_threads_enter ();
//	gtk_box_pack_start (GTK_BOX (app.status_bar_box3), label, FALSE, FALSE, 0);
	gtk_container_add (GTK_CONTAINER (app.status_bar_box3), label);
	gtk_widget_show_all (app.status_bar_box3);
	gdk_threads_leave ();
}
/**
@brief idle function to clear statusline message
@param data UNUSED data specified when idle was setup
@return FALSE always
*/
static gboolean _e2_window_unadvise (gpointer data)
{
	gdk_threads_enter ();
	gtk_widget_hide (app.status_bar_box3);
	gdk_threads_leave ();
//	GList *children = GTK_BOX (app.status_bar_box3)->children;
//	GtkWidget *label = ((GtkBoxChild *) children->data)->widget;
//	gtk_container_remove (GTK_CONTAINER (app.status_bar_box3), label);
	GList *children = gtk_container_get_children (GTK_CONTAINER (app.status_bar_box3));
	if (children != NULL)
	{
		if (GTK_IS_WIDGET (children->data))
			gtk_widget_destroy (children->data);	//FIXME make this a more-friendly Q
		g_list_free (children);
	}
	return FALSE;
}
/**
@brief remove statusline message
In case this change is done at a busy time for X/gdk, it's done asynchronously
@return
*/
void e2_window_clear_status_message (void)
{
	g_idle_add ((GSourceFunc) _e2_window_unadvise, NULL);
}
/**
@brief create and show main window
This is called only at session-start, from main ()

@param rt ptr to window data struct

@return
*/
void e2_window_create (E2_WindowRuntime *rt)
{
	_e2_window_set_icon ();

	//setup main window
	app.main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title (GTK_WINDOW (app.main_window), PROGNAME);
//	gtk_window_set_role (GTK_WINDOW (app.main_window), "main");
//	gtk_window_set_wmclass (GTK_WINDOW (app.main_window), "main", BINNAME);
	gtk_widget_set_size_request (app.main_window, 0, 0);	//window as small as possible
	//default size 640 x 480
	//FIXME these are wrong when window is maximised
#ifdef USE_GTK2_18
	e2_cache_int_register ("window-width", &app.main_alloc.width, 640);
	e2_cache_int_register ("window-height", &app.main_alloc.height, 480);
#else
	e2_cache_int_register ("window-width", &app.main_window->allocation.width, 640);
	e2_cache_int_register ("window-height", &app.main_window->allocation.height, 480);
#endif
	gtk_window_set_default_size (GTK_WINDOW (app.main_window),
#ifdef USE_GTK2_18
		app.main_alloc.width, app.main_alloc.height);
#else
		app.main_window->allocation.width, app.main_window->allocation.height);
#endif
	gtk_window_set_resizable (GTK_WINDOW (app.main_window), TRUE);
#ifdef E2_COMPOSIT
	e2_window_set_opacity (app.main_window, -1);
#endif
	//arrange for translation of mod-alphabetic-keycodes from locale to ascii
	g_signal_connect (G_OBJECT (app.main_window), "key-press-event",
		G_CALLBACK (e2_utils_key_translate_cb), GUINT_TO_POINTER(1));	//non-NULL data to avoid disconnection during bindings setup
/*	//create top-level bindings
	e2_keybinding_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	//(must be done before file- or output-pane contents are created)
	gchar *category = g_strconcat (_C(17), ".", _C(23),NULL);
	e2_keybinding_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
#ifdef E2_MOUSECUSTOM
	e2_mousebinding_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	e2_mousebinding_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
# ifdef E2_PTRGESTURES
	e2_mousegesture_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	e2_mousegesture_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
# endif
#endif
	g_free (category);
*/
	g_signal_connect (G_OBJECT (app.main_window), "show",
		G_CALLBACK (_e2_window_show_cb), rt);
	//arrange to clean up gracefully
	g_signal_connect (G_OBJECT (app.main_window), "delete-event",
		G_CALLBACK (_e2_window_gone_cb), NULL); //NULL data = optional cancellation of window-close
/*FIXME if the system needs shutdown-feedback. support
	different processes for user- and system-initiated shutdowns?
	g_signal_connect (G_OBJECT (app.main_window),  ??,
		G_CALLBACK (system_shutdown), NULL); */
	//trap signals that might (but probably don't) get issued when a
	//session-manager initiates a shutdown
//	g_signal_connect (GTK_OBJECT (app.main_window), "destroy",
//		G_CALLBACK (_e2_window_destroy_cb), NULL);
	g_signal_connect (G_OBJECT (app.main_window), "destroy-event",
		G_CALLBACK (_e2_window_gone_cb), (gpointer)TRUE);

	app.hbox_main = gtk_hbox_new (FALSE, 0);
	app.vbox_main = gtk_vbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (app.main_window), app.hbox_main);
	gtk_box_pack_start (GTK_BOX (app.hbox_main), app.vbox_main, TRUE, TRUE, 0);

#ifdef E2_RAINBOW
	e2_option_color_filetypes_sync ();
#endif

	//file panes infrastructure
	e2_cache_double_register ("file-pane-ratio-last", &rt->panes_paned_ratio_last, 0.55);
	e2_cache_double_register ("file-pane-ratio", &rt->panes_paned_ratio, 0.55);
	_e2_window_create_pane_boxes (rt);	//setup paned widget, with approximately 'correct' ratio

	//output pane (which can be completed relatively quickly)
	e2_cache_double_register ("output-pane-ratio-last", &rt->output_paned_ratio_last, 0.85);
	e2_cache_double_register ("output-pane-ratio", &rt->output_paned_ratio, 0.85);
	e2_cache_int_register ("output-pane-tabs", &app.tabcount, 1);
	GtkWidget *wid = e2_output_initialise ();
	//set visibility state
	app.output.visible = (rt->output_paned_ratio != 1.0);
	rt->output_paned = gtk_vpaned_new ();
#ifdef USE_GTK2_18
	gtk_widget_set_can_focus (rt->output_paned, FALSE);
#else
	GTK_WIDGET_UNSET_FLAGS (rt->output_paned, GTK_CAN_FOCUS);
#endif
	gtk_paned_pack1 (GTK_PANED (rt->output_paned), rt->panes_outer_box, TRUE, TRUE);
	gtk_paned_pack2 (GTK_PANED (rt->output_paned), wid, TRUE, TRUE);

	gtk_paned_set_position (GTK_PANED (rt->output_paned),
#ifdef USE_GTK2_18
		app.main_alloc.height
#else
		app.main_window->allocation.height
#endif
		* rt->output_paned_ratio);

	gtk_box_pack_start (GTK_BOX (app.vbox_main), rt->output_paned, TRUE, TRUE, 0);

	gtk_widget_show_all (app.hbox_main); //before panes (with toggle-buttons)

	//do this registration here, rather that in the pane context,
	//as it covers both panes, and to avoid attempted re-registers
	//whenever the main window is re-created
/*	gint i; gint j;
	cols_data = NULL;
	E2_Cache *cache = e2_cache_list_register ("columns-data", &cols_data);
	//before writing cache, this will update list data
	cache->sync_func = e2_fileview_update_col_cachedata;
	if (cols_data == NULL)
	{  //there was no cache found, so setup defaults
		for (j = 0; j < 2; j++)
		{ for (i = 0; i < MAX_COLUMNS; i++) {
			stored_col_order[j][i] = i;
			col_width_store[j][i] = e2_all_columns[i].size;
		}}
	}
	else //column data found in cache
	{
		//get data from cached list (no data checks!!)
		//list format = pane1 { order, width, ...} pane2 {order, width, ... }
		GList *iterator = g_list_first (cols_data);
		for (j = 0; j < 2; j++)
		{ for (i = 0; i < MAX_COLUMNS; i++) {
			stored_col_order[j][i] = atoi (iterator->data);
			iterator = iterator->next;
			col_width_store[j][i] = atoi (iterator->data);
			iterator = iterator->next;
		}}
	}
*/
	//register columns data, with 2 panes sequential
	gint orders[] = {0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7};
	e2_cache_array_register ("columns-order", MAX_COLUMNS*2, (gint *)stored_col_order, orders);
	gint widths [] = {
		e2_all_columns[0].size,
		e2_all_columns[1].size,
		e2_all_columns[2].size,
		e2_all_columns[3].size,
		e2_all_columns[4].size,
		e2_all_columns[5].size,
		e2_all_columns[6].size,
		e2_all_columns[7].size,
		e2_all_columns[0].size,
		e2_all_columns[1].size,
		e2_all_columns[2].size,
		e2_all_columns[3].size,
		e2_all_columns[4].size,
		e2_all_columns[5].size,
		e2_all_columns[6].size,
		e2_all_columns[7].size
		};
	e2_cache_array_register ("columns-width", MAX_COLUMNS*2, (gint *)col_width_store, widths);

	e2_cache_int_register ("pane1-sort-column", &app.pane1.view.sort_column, FILENAME);
	e2_cache_int_register ("pane2-sort-column", &app.pane2.view.sort_column, FILENAME);
	e2_cache_int_register ("pane1-sort-direction", (gint *) &app.pane1.view.sort_order,
		GTK_SORT_ASCENDING);
	e2_cache_int_register ("pane2-sort-direction", (gint *) &app.pane2.view.sort_order,
		GTK_SORT_ASCENDING);
	e2_cache_bool_register ("pane1-show-hidden", &app.pane1.view.show_hidden, FALSE);
	e2_cache_bool_register ("pane2-show-hidden", &app.pane2.view.show_hidden, FALSE);
	//to avoid slow startup, any use of cached vfs data (including button state)
	//is not done here, but is deferred until an idle when main loop is first started

//#if sizeof(time_t) == 8
//#define LONGTIME
//#endif
	time_t _now = time (NULL);
	if (_now == (time_t) -1)
		_now = 0;

	e2_cache_bool_register ("pane1-filter-names", &app.pane1.view.name_filter.active, FALSE);
	e2_cache_str_register ("pane1-filter-nametype", &app.pane1.view.name_filter.patternptr, "*");
	e2_cache_bool_register ("pane1-filter-nameinvert", &app.pane1.view.name_filter.invert_mask, FALSE);
	e2_cache_bool_register ("pane1-filter-namecase", &app.pane1.view.name_filter.case_sensitive, TRUE);

	e2_cache_bool_register ("pane1-filter-dates", &app.pane1.view.date_filter.active, FALSE);
//#ifdef LONGTIME
	e2_cache_time_register ("pane1-filter-datebase", &app.pane1.view.date_filter.time, _now);
//#else
//	e2_cache_int_register ("pane1-filter-datebase", (gint *) &app.pane1.view.date_filter.time, _now);
//#endif
	e2_cache_int_register ("pane1-filter-daterel", (gint *) &app.pane1.view.date_filter.op, GT);
	e2_cache_int_register ("pane1-filter-datetype", (gint *)&app.pane1.view.date_filter.time_type, MTIME);

	e2_cache_bool_register ("pane1-filter-sizes", &app.pane1.view.size_filter.active, FALSE);
	e2_cache_int_register ("pane1-filter-sizebase", (gint *) &app.pane1.view.size_filter.size, 0);	//size_t=guint
	e2_cache_int_register ("pane1-filter-sizerel", (gint *) &app.pane1.view.size_filter.op, GT);

	e2_cache_bool_register ("pane1-filter-dirs", &app.pane1.view.filter_directories, FALSE);

	e2_cache_bool_register ("pane2-filter-names", &app.pane2.view.name_filter.active, FALSE);
	e2_cache_str_register ("pane2-filter-nametype", &app.pane2.view.name_filter.patternptr, "*");
	e2_cache_bool_register ("pane2-filter-nameinvert", &app.pane2.view.name_filter.invert_mask, FALSE);
	e2_cache_bool_register ("pane2-filter-namecase", &app.pane2.view.name_filter.case_sensitive, TRUE);

	e2_cache_bool_register ("pane2-filter-dates", &app.pane2.view.date_filter.active, FALSE);
//#ifdef LONGTIME
	e2_cache_time_register ("pane2-filter-datebase", &app.pane2.view.date_filter.time, _now);
//#else
//	e2_cache_int_register ("pane2-filter-datebase", (gint *) &app.pane2.view.date_filter.time, _now);
//#endif
	e2_cache_int_register ("pane2-filter-daterel", (gint *) &app.pane2.view.date_filter.op, GT);
	e2_cache_int_register ("pane2-filter-datetype", (gint *) &app.pane2.view.date_filter.time_type, MTIME);

	e2_cache_bool_register ("pane2-filter-sizes", &app.pane2.view.size_filter.active, FALSE);
	e2_cache_int_register ("pane2-filter-sizebase", (gint *) &app.pane2.view.size_filter.size, 0);
	e2_cache_int_register ("pane2-filter-sizerel", (gint *) &app.pane2.view.size_filter.op, GT);

	e2_cache_bool_register ("pane2-filter-dirs", &app.pane2.view.filter_directories, FALSE);

	//get working copies
/*	g_strlcpy (app.pane1.view.name_filter.pattern,
		app.pane1.view.name_filter.patternptr,
		sizeof (app.pane1.view.name_filter.pattern));
	g_strlcpy (app.pane2.view.name_filter.pattern,
		app.pane2.view.name_filter.patternptr,
		sizeof (app.pane2.view.name_filter.pattern)); */

	g_hook_list_init (&app.hook_pane_focus_changed, sizeof (GHook));

	e2_pane_create (&app.pane1);
	e2_pane_create (&app.pane2);

	gtk_paned_pack1 (GTK_PANED (rt->panes_paned), app.pane1.outer_box, TRUE, TRUE);
	gtk_paned_pack2 (GTK_PANED (rt->panes_paned), app.pane2.outer_box, TRUE, TRUE);

	if (rt->panes_paned_ratio > 0.05)
	{
		curr_pane = &app.pane1;
		other_pane = &app.pane2;
		curr_view = &app.pane1.view;
		other_view = &app.pane2.view;
	}
	else
	{
		curr_pane = &app.pane2;
		other_pane = &app.pane1;
		curr_view = &app.pane2.view;
		other_view = &app.pane1.view;
	}
	//show which pane is active
	e2_pane_flag_active ();
	//setup correct focus
	gtk_widget_grab_focus (curr_view->treeview);
	//other toolbars
	e2_toolbar_create (&app.toolbar);
	e2_toolbar_create (&app.commandbar);

	if (e2_option_bool_get_direct (app.commandbar.show)) //FIXME button could be on any bar
	{
		//show corrrect output pane toggle buttons according to cached pane ratio
		if (app.window.output_paned_ratio < 0.001)
			e2_toolbar_toggle_button_set_state
				(toggles_array [E2_TOGGLE_OUTPUTFULL], TRUE);
		else if (app.window.output_paned_ratio > 0.999)
			e2_toolbar_toggle_button_set_state
				(toggles_array [E2_TOGGLE_OUTPUTSHADE], TRUE);
	}

	//status bar
//	wid = gtk_table_new (1, 3, FALSE);
	wid = gtk_hbox_new (FALSE, E2_PADDING_SMALL);
	gtk_box_pack_start (GTK_BOX (app.vbox_main), wid, FALSE, FALSE, 0);
	gtk_widget_show (wid);
	//find current user name (can't use environment string ...)
	struct passwd *pw_buf;
	uid_t myuid = getuid ();
	GString *who_where = g_string_new ("");
	while ((pw_buf = getpwent ()) != NULL)
		if (pw_buf->pw_uid == myuid)
			break;
	if (pw_buf != NULL)
		who_where = g_string_assign (who_where, pw_buf->pw_name);
	//find current machine name
	const gchar *env = g_getenv ("HOSTNAME");
	if (env != NULL)
		g_string_append_printf (who_where, " @ %s", env);	//no translation
//#ifdef E2_POLKIT
//# define wid2 app.status_bar_label1
//#else
	GtkWidget *wid2;
//#endif
	wid2 = gtk_label_new (who_where->str);
	g_string_free (who_where, TRUE);
//	gtk_misc_set_alignment (GTK_MISC (wid2), 0.0, 0.5);
//	gtk_table_attach (GTK_TABLE(wid), wid2, 0, 1, 0, 1,
//		GTK_SHRINK, GTK_SHRINK,
//		E2_PADDING_SMALL, 0);
	gtk_box_pack_start (GTK_BOX (wid), wid2, FALSE, FALSE, 10);
	gtk_widget_show (wid2);
//#ifdef E2_POLKIT
//# undef wid2
//#endif
	app.status_bar_label2 = gtk_label_new (" ");
//	gtk_misc_set_alignment (GTK_MISC (app.status_bar_label2), 0.5, 0.5);
//	gtk_box_pack_start (GTK_BOX (app.vbox_main), app.status_bar_label2, FALSE, TRUE, 0);
//	gtk_table_attach (GTK_TABLE(wid), app.status_bar_label2, 1, 2, 0, 1,
//		GTK_EXPAND, GTK_SHRINK,
//		E2_PADDING_SMALL, 0);
	gtk_box_pack_start (GTK_BOX (wid), app.status_bar_label2, FALSE, FALSE, 0);
	gtk_widget_show (app.status_bar_label2);
	//create hbox for any other status indicator eg for progress bars
	//not shown until needed
	app.status_bar_box3 = gtk_hbox_new (FALSE, E2_PADDING_SMALL);
	gtk_box_pack_end (GTK_BOX (wid), app.status_bar_box3, FALSE, TRUE, E2_PADDING_LARGE);
//	gtk_table_attach (GTK_TABLE(wid), app.status_bar_box3, 2, 3, 0, 1,
//		GTK_EXPAND, GTK_SHRINK,
//		E2_PADDING_SMALL, 0);

	gdk_threads_enter (); //must close BGL, even at session start
	gtk_widget_show (app.main_window);
	gdk_threads_leave ();
}
/**
@brief re-create main window

This is used after config dialog, or detection of a config file that is updated
(e.g. by another instance of e2), or a change of file-panes direction

@param rt ptr to window data struct

@return
*/
void e2_window_recreate (E2_WindowRuntime *rt)
{
	printd (DEBUG, "recreate main window");

	//prevent any new refresh from starting during this rebuild
	e2_filelist_disable_refresh ();

	//wait until any in-progress filelist process is completed
	while (TRUE)
	{
		gboolean busy;
//		LISTS_LOCK
		busy = g_atomic_int_get (&curr_view->listcontrols.cd_working)
			|| g_atomic_int_get (&curr_view->listcontrols.refresh_working)
			|| g_atomic_int_get (&other_view->listcontrols.cd_working)
			|| g_atomic_int_get (&other_view->listcontrols.refresh_working);
//		LISTS_UNLOCK
		if (busy)
			usleep (50000);
		else
		{
			//save current selections
			if (curr_view->selected_names != NULL)
				//setup to get rid of any old hash
				g_idle_add_full (G_PRIORITY_LOW,
					(GSourceFunc) e2_fileview_treehash_free, curr_view->selected_names, NULL);
			curr_view->selected_names = e2_fileview_log_selected_names (curr_view);
			if (other_view->selected_names != NULL)
				g_idle_add_full (G_PRIORITY_LOW,
					(GSourceFunc) e2_fileview_treehash_free, other_view->selected_names, NULL);
			other_view->selected_names = e2_fileview_log_selected_names (other_view);
			break;
		}
	}

	//clear hooks
	if (app.pane1.hook_change_dir.is_setup)
		g_hook_list_clear (&app.pane1.hook_change_dir);
	if (app.pane2.hook_change_dir.is_setup)
		g_hook_list_clear (&app.pane2.hook_change_dir);
	//CHECKME
	if (app.pane1.view.hook_refresh.is_setup)
		g_hook_list_clear (&app.pane1.view.hook_refresh);
	if (app.pane2.view.hook_refresh.is_setup)
		g_hook_list_clear (&app.pane2.view.hook_refresh);

	//make sure current cols data are used for the window recreate
	e2_fileview_update_col_cachedata ();

	//save current liststores so data can be reinstated for updating
	GtkListStore *curr_store = curr_view->store;
	g_object_ref (G_OBJECT (curr_store));
	GtkListStore *other_store = other_view->store;
	g_object_ref (G_OBJECT (other_store));

	//save current scroll positions
	gint curr_xscroll, other_xscroll;
	gint curr_yscroll, other_yscroll;
	//FIXME this func always finds column 0
	e2_fileview_get_scroll_data (curr_view, &curr_xscroll, &curr_yscroll);
	e2_fileview_get_scroll_data (other_view, &other_xscroll, &other_yscroll);

	GtkAdjustment *adj;
	gdouble curr_thumbx, curr_upperx, other_thumbx, other_upperx;
	adj = gtk_scrolled_window_get_hadjustment
		(GTK_SCROLLED_WINDOW (curr_pane->pane_sw));
	curr_thumbx = gtk_adjustment_get_value (adj);
#ifdef USE_GTK2_14
	curr_upperx = gtk_adjustment_get_upper (adj);
#else
	curr_upperx = adj->upper;
#endif
	adj = gtk_scrolled_window_get_hadjustment
		(GTK_SCROLLED_WINDOW (other_pane->pane_sw));
	other_thumbx = gtk_adjustment_get_value (adj);
#ifdef USE_GTK2_14
	other_upperx = gtk_adjustment_get_upper (adj);
#else
	other_upperx = adj->upper;
#endif

	//ensure all toolbar toggle buttons are recreated with their current state
//downstream e2_toolbar_toggle_buttons_set_destroyed (NULL);
	//destroy the widgets
	//toolbar(s) may be outside rt->panes_outer_box and we want to zap their data,
	//so destroy them independently
	e2_toolbar_destroy (&app.pane1.toolbar);
	e2_toolbar_destroy (&app.pane2.toolbar);
	e2_toolbar_destroy (&app.toolbar);
	e2_toolbar_destroy (&app.commandbar);
	gtk_widget_destroy (rt->panes_outer_box);

#ifdef E2_COMPOSIT
	e2_window_set_opacity (app.main_window, -1);
#endif

/*	//create top-level bindings
	e2_keybinding_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	gchar *category = g_strconcat (_C(17), ".", _C(23),NULL);
	e2_keybinding_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
#ifdef E2_MOUSECUSTOM
	e2_mousebinding_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	e2_mousebinding_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
# ifdef E2_PTRGESTURES
	e2_mousegesture_enrol (app.main_window, _C(17), (void(*)(E2_OptionSet*))NULL);
	e2_mousegesture_enrol (app.main_window, category, (void(*)(E2_OptionSet*))NULL);
# endif
#endif
	g_free (category);
*/
#ifdef USE_GTK2_18
	gtk_widget_get_allocation (app.main_window, &app.main_alloc);
#endif
	_e2_window_create_pane_boxes (rt);
	gtk_widget_show_all (rt->panes_outer_box); //before bars with toggles

	e2_pane_create_part (&app.pane1);
	e2_toolbar_rebadge (&app.pane1.toolbar);
	e2_pane_create_part (&app.pane2);
	e2_toolbar_rebadge (&app.pane2.toolbar);

	e2_toolbar_create (&app.toolbar);
	e2_toolbar_create (&app.commandbar);

	gtk_paned_pack1 (GTK_PANED (rt->panes_paned), app.pane1.outer_box, TRUE, TRUE);
	gtk_paned_pack2 (GTK_PANED (rt->panes_paned), app.pane2.outer_box, TRUE, TRUE);
	gtk_paned_pack1(GTK_PANED (rt->output_paned), rt->panes_outer_box, TRUE, TRUE);

	//mark the active pane
	e2_pane_flag_active ();

	WAIT_FOR_EVENTS; //make sure pane parameters are set, before the cb
	_e2_window_show_cb (NULL, rt);

	e2_keybinding_register_all ();
#ifdef E2_MOUSECUSTOM
	e2_mousebinding_register_all (); //includes gestures if relevant
#endif
	e2_alias_sync (&app.aliases);

	//reinstate former liststore, so its data can be recovered
	gtk_tree_view_set_model (GTK_TREE_VIEW (curr_view->treeview), NULL);
	curr_view->store = curr_store;
	//FIXME block new filelist if currently busy
//	LISTS_LOCK
	curr_view->listcontrols.refreshtype = E2_RECREATE;
//	LISTS_UNLOCK
	e2_fileview_prepare_list (curr_view);	//apply a rebuilt liststore

	//revert the vert scroll
	//needs to be scroll-to-position  - maybe treeview is not
	//immediately ready to accept vertical adjustment change ?
	if (e2_fileview_scroll_to_position (curr_view, 0, curr_yscroll))
	{
		//revert the horiz scroll too
		adj = gtk_scrolled_window_get_hadjustment
			(GTK_SCROLLED_WINDOW (curr_pane->pane_sw));
#ifdef USE_GTK2_14
		gtk_adjustment_set_upper (adj, curr_upperx);
#else
		adj->upper = curr_upperx;
#endif
		gtk_adjustment_set_value (adj, curr_thumbx);
		gtk_adjustment_changed (adj);
	}
	//clear out any old selection
//	gtk_tree_selection_unselect_all (curr_view->selection);
	e2_fileview_reselect_names (curr_view);	//try to reselect items, and at least clean the hash
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
//	printd (DEBUG, "finished new selection for %s", view->dir);
#endif
	gtk_tree_view_set_model (GTK_TREE_VIEW (other_view->treeview), NULL);
	other_view->store = other_store;
	//FIXME block new filelist if currently busy
//	LISTS_LOCK
	other_view->listcontrols.refreshtype = E2_RECREATE;
//	LISTS_UNLOCK
	e2_fileview_prepare_list (other_view);	//apply a rebuilt liststore

	if (e2_fileview_scroll_to_position (other_view, 0, other_yscroll))
	{
		adj = gtk_scrolled_window_get_hadjustment
			(GTK_SCROLLED_WINDOW (other_pane->pane_sw));
#ifdef USE_GTK2_14
		gtk_adjustment_set_upper (adj, other_upperx);
#else
		adj->upper = other_upperx;
#endif
		gtk_adjustment_set_value (adj, other_thumbx);
		gtk_adjustment_changed (adj);
	}
	//clear out any old selection
//	gtk_tree_selection_unselect_all (other_view->selection);
	e2_fileview_reselect_names (other_view);	//try to reselect items, then clean the hash
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
//	printd (DEBUG, "finished new selection for %s", view->dir);
#endif
	//set output visibility flag
	app.output.visible = (rt->output_paned_ratio != 1.0);
	GtkCornerType where = (GtkCornerType) e2_option_sel_get ("scrollbar-position");
	//update each bar position
	GList *member;
	for (member = app.tabslist; member != NULL; member = member->next)
		gtk_scrolled_window_set_placement (
		GTK_SCROLLED_WINDOW (((E2_OutputTabRuntime *)member->data)->scroll), where);
#ifdef ADJACENT_TABS
	if (where == GTK_CORNER_TOP_LEFT || where == GTK_CORNER_BOTTOM_LEFT)
		gtk_notebook_set_tab_pos (GTK_NOTEBOOK (app.outbook), GTK_POS_RIGHT);
	else
		gtk_notebook_set_tab_pos (GTK_NOTEBOOK (app.outbook), GTK_POS_LEFT);
#else
	if (where == GTK_CORNER_TOP_LEFT || where == GTK_CORNER_BOTTOM_LEFT)
		gtk_notebook_set_tab_pos (GTK_NOTEBOOK (app.outbook), GTK_POS_LEFT);
	else
		gtk_notebook_set_tab_pos (GTK_NOTEBOOK (app.outbook), GTK_POS_RIGHT);
#endif
	e2_output_update_style ();
	e2_filelist_enable_refresh ();
	gtk_widget_grab_focus (curr_view->treeview);
}
/**
@brief register main-window-related actions

@return
*/
void e2_window_actions_register (void)
{	//all action-name strings freeable
	E2_Action actions[] =
	{
	{g_strconcat(_A(10),".",_A(31),NULL),_e2_window_adjust_output_ratio_action,TRUE, E2_ACTION_TYPE_ITEM,0,&app.window, NULL},
	{g_strconcat(_A(14),".",_A(31),NULL),_e2_window_adjust_pane_ratio_action,  TRUE, E2_ACTION_TYPE_ITEM,0,&app.window, NULL},
	{g_strconcat(_A(17),".",_A(48),NULL),_e2_window_toggle_full_screen,        FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
	{g_strconcat(_A(14),".",_A(99),NULL),_e2_window_toggle_panes_direction,    FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
	//these are "self-managed" toggle actions, with names already logged
	{g_strdup (toggles_array[E2_TOGGLE_OUTPUTSHADE]),
										_e2_window_toggle_visible_output,      FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
	{g_strdup (toggles_array[E2_TOGGLE_OUTPUTFULL]),
										_e2_window_toggle_full_output,         FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
 	{g_strdup (toggles_array[E2_TOGGLE_PANE1FULL]),
										_e2_window_toggle_full_pane1,          FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
 	{g_strdup (toggles_array[E2_TOGGLE_PANE2FULL]),
										_e2_window_toggle_full_pane2,          FALSE,E2_ACTION_TYPE_ITEM,0,NULL, NULL},
	};
	guint i, count = sizeof (actions)/sizeof (E2_Action);
	for (i = 0; i < count; i++)
		e2_action_register (&actions[i]);
}
