/* $Id: e2_window.c 850 2008-04-07 10:33:34Z tpgww $

Copyright (C) 2004-2008 tooar <tooar@gmx.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, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@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"

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 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 the correct position
(this is before pane content is added, so that un-necessary content
mapping is minimized)
All created widgets are 'shown'

@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 = app.main_window->allocation.height - 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 = app.main_window->allocation.width - 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);
	rt->panes_paned_pos = gtk_paned_get_position (GTK_PANED (rt->panes_paned));

	gtk_box_pack_start_defaults (GTK_BOX (rt->panes_outer_box), rt->panes_inner_box);
	gtk_box_pack_start_defaults (GTK_BOX (rt->panes_inner_box), rt->panes_paned);

	gtk_widget_show (rt->panes_outer_box);
	gtk_widget_show (rt->panes_inner_box);
	gtk_widget_show (rt->panes_paned);
}
/**
@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);
	return ((gdouble) pos / _paned->max_position);
}
/**
@brief calculate position of the divider for @a paned

This converts a string fraction or percentage (with "%" suffix)
to an integer, which corresponds to the specified fraction or
percentage of @a paned ->max_position

@param str string with value to be converted
@param paned the paned widget to which the value applies

@return integer value of the 'position' of the paned divider, or -2 if invalid string is provided
*/
static gint _e2_window_get_pos_from_str (gchar *str, GtkPaned *paned)
{
	if (str == NULL)
		return -2;

	gboolean percent = g_str_has_suffix (str, "%");

	gchar *end = NULL;
	gdouble num = g_ascii_strtod (str, &end);

	if (end == str)
		return -2;

	gint retval;
	if (percent)
		retval = paned->max_position * (num / 100.0);
	else
		retval = paned->max_position * num;

	return retval;
}
/**
@brief change position of the separator between window file-panes or between file & output panes

Value(s) provided in @a arg is (are both) a proportion of the relevant window
dimension, or (the first one) may be * which causes the stored prior value
of the fraction to be used
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 string with one (or two comma-separated) ratios in [0-1]

@return
*/
static void _e2_window_adjust_panes_ratio (GtkPaned *paned, gint *pos,
	gdouble *ratio_last, gchar *arg)
{
	printd (DEBUG, "_e2_window_adjust_panes_ratio (paned:,pos:%d,ratio_last:%f,arg:%s)",
		*pos, *ratio_last, arg);

	if (arg == NULL)
		return;

	gint cur;

	gchar *second = strchr (arg, ',');	//if always ascii ',', don't need g_utf8_strchr()
	if (second != NULL)
	{
		glong offset =  g_utf8_pointer_to_offset (arg, second);
		gchar *temp_str = e2_utf8_ndup (arg, offset);
		gint one = _e2_window_get_pos_from_str (temp_str, paned);
		g_free (temp_str);
		gint two = _e2_window_get_pos_from_str (second + 1, paned);  //assumes the ',' is a single byte long !
		if (two == -2)	//invalid 2nd value provided
			return;
		cur = gtk_paned_get_position (paned);
		if (cur == two)
		{
			if (one == -2)
			{	//invalid 1st value (probably *) provided
				if ((*pos == paned->max_position) || (*pos == paned->min_position))
					cur = paned->max_position * *ratio_last;
				else
					cur = *pos;
			}
			else
				cur = one;
		}
		else
		{
			if ((cur != 0) && (cur != paned->max_position))
				*pos = cur;
			cur = two;
		}
	}
	else //there is only one parameter provided
	{
		cur = _e2_window_get_pos_from_str (arg, paned);
		if (cur == -2)
		{	//invalid value (probably *) provided
			if ((*pos == paned->max_position) || (*pos == paned->min_position))
				cur = paned->max_position * *ratio_last;
			else
				cur = *pos;
		}
		else
			*pos = cur;
	}
	gtk_paned_set_position (paned, cur);

	gdouble ratio_now = cur / paned->max_position;
	if (ratio_now > 0.001 && ratio_now < 0.999)
		*ratio_last = ratio_now;

	if (paned == GTK_PANED (app.window.panes_paned))
	{	//do this only for filelist divider changes (may hang for output pane divider)
		if (ratio_now > 0.99 && GTK_WIDGET_HAS_FOCUS (app.pane2_view.treeview))
		{
			WAIT_FOR_EVENTS	//note - expects BGL closed
			gtk_widget_grab_focus (app.pane1_view.treeview);
		}
		else if (ratio_now < 0.01 && GTK_WIDGET_HAS_FOCUS (app.pane1_view.treeview))
		{
			WAIT_FOR_EVENTS
			gtk_widget_grab_focus (app.pane2_view.treeview);
		}
	}
}
/**
@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 (gchar *arg)
{
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_pos, &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_pos, &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_pos, &app.window.output_paned_ratio_last,
			"1");
	}
	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
		real_width = app.main_window->allocation.width;
		real_height = app.main_window->allocation.height;
		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 (app.main_window->window);
	//notice future window-state changes
	g_signal_connect (G_OBJECT(app.main_window), "window-state-event",
		G_CALLBACK(_e2_window_change_cb), NULL);
	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);
	rt->panes_paned_pos = gtk_paned_get_position (GTK_PANED (rt->panes_paned));
	rt->output_paned_pos = gtk_paned_get_position (GTK_PANED (app.window.output_paned));
	//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 need be (ie 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;
}
/* *
@brief callback for main window "destroy" signal
@param object UNUSED the window object
@param user_data UNUSED
@return
*/
/*static void _e2_window_destroy_cb (GtkObject *object, gpointer user_data)
{
	printd (DEBUG, "main window \"destroy\" signal callback");
	e2_main_closedown (TRUE, TRUE, TRUE);
}
*/
#ifdef E2_COMPOSIT
/* *
@brief callback for "expose-event" signal on @a widget
This is called when we need to draw the window's contents. The X server sends
an expose event when the window becomes visible on screen, meaning we need to
draw the window's contents.  On a composited desktop expose is normally only
sent when the window is put on the screen. (On a non-composited desktop it can
be sent whenever the window is uncovered by another.)
@param widget window widget
@param event pointer to event data struct
@param data pointerised opacity level, 0 (transparent) to 100 (opaque) or -1 to use config default

@return FALSE always, to propogate the event to other handlers
*/
/*static gboolean _e2_window_expose_cb (GtkWidget *widget, GdkEventExpose *event,
	gpointer data)
{
	cairo_t *cr = gdk_cairo_create (widget->window);
	if  (cr == NULL)
		return FALSE;

	cairo_rectangle (cr,
		event->area.x, event->area.y, event->area.width, event->area.height);
	cairo_clip (cr);

	//draw the background
	gint level = (GPOINTER_TO_INT (data) == -1) ?
		e2_option_int_get ("window-bleed"):
		GPOINTER_TO_INT (data);
	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
/ *	cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); //fully transparent
	cairo_set_source_rgba (cr, 1.0, 1.0, 1.0); //opaque white
 or partially transparent
	cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, (gfloat) level / 100.0);	//black transparent as wanted
	//CHECKME discover destktop background "general" shade and/or current window source
	//and set background accordingly ?
	if (<dark background>)
		cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.25);
	else	//light desktop background
		cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.05);

	cairo_set_source_rgba (cr, R, G, B, A);


	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
	cairo_paint (cr);
* /
	cairo_paint_with_alpha (cr, (gfloat) level / 100.0);

	cairo_destroy (cr);
	return FALSE;
}
*/
/* *
@brief callback for "screen-changed" signal on @a widget
GTK supports migration of running applications between X servers, which might not
support the same features (and in partucular, an alpha channel).
This signal arrives when the display on which @a widget is drawn has changed.
So we need to check whether it's still ok for transparent windows, and respond
accordingly.
@param widget the widget for the window whose screen changed, or whose opacity is being changed
@param old_screen pointer to screen before the change
@param data pointerised opacity level, 0 (transparent) to 100 (opaque) or -1 to use config default

@return
*/
/*static void _e2_window_screen_changed_cb (GtkWidget *widget,
	GdkScreen *old_screen, gpointer data)
{
	GdkScreen *new_screen;
	GdkColormap *colormap;
//#ifdef USE_GTK2_12
//	GdkDisplay *display = ; what for old screen ??
//	if gdk_display_supports_composite (display))
//#else
#if defined (USE_GTK2_10)
	if (gdk_screen_is_composited (old_screen))
#else //def USE_GTK2_8
	//to check if the display supports alpha channels, check the alpha-colormap
	colormap = gdk_screen_get_rgba_colormap (old_screen);
	if (colormap != NULL)
#endif
	{	//former screen allowed compositing
#ifdef USE_GTK2_12
		if (!gtk_widget_is_composited (widget))
#elif defined (USE_GTK2_10)
		new_screen = gtk_widget_get_screen (widget);
		if (!gdk_screen_is_composited (new_screen))
#else //def USE_GTK2_8
		new_screen = gtk_widget_get_screen (widget);
		colormap = gdk_screen_get_rgba_colormap (new_screen);
		if (colormap == NULL)	//this test not entirely sufficient (need relevant compositor and WM)
#endif
		{	//but this one doesn't, make some changes
#ifdef USE_GTK2_12
/ *CHECKME
			GtkWindowType wtype;
			g_object_get (G_OBJECT (widget), "type", &wtype, NULL);
			if (wtype == GTK_WINDOW_TOPLEVEL)
				gdk_window_set_opacity (widget->window, gdouble opacity);
			else //<child window
* /
				gdk_window_set_composited (widget->window, FALSE);
#endif
			g_signal_handlers_disconnect_by_func (G_OBJECT (widget),
				_e2_window_expose_cb, NULL);
			gtk_widget_set_app_paintable (widget, FALSE);
#ifdef USE_GTK2_12
			new_screen = gtk_widget_get_screen (widget);;
#endif
			colormap = gdk_screen_get_rgb_colormap (new_screen);
			gtk_widget_set_colormap (widget, colormap);
			if (GTK_WIDGET_VISIBLE (widget))
			{	//trigger a window repaint
				GdkRegion *region = gdk_region_rectangle (&widget->allocation);
				gdk_window_invalidate_region (widget->window, region, TRUE);
				gdk_region_destroy (region);	//CHECKME
			}
		}
		else	//old and new screens both support compositing
			if (new_screen == old_screen)	//setting or changing opacity level, not a real callback
		{
//#ifdef USE_GTK2_10
//#else //2.8
			//FIXME do much the same as below
//#endif
			goto setup;
		}
	}
	else	//the old screen was not composited
#ifdef USE_GTK2_12
		if (gtk_widget_is_composited (widget))
#elif defined (USE_GTK2_10)
	{
		new_screen = gtk_widget_get_screen (widget);
		if (gdk_screen_is_composited (new_screen))
#else
	{	//but the new one is, so setup for that
		new_screen = gtk_widget_get_screen (widget);
		colormap = gdk_screen_get_rgba_colormap (new_screen);
		if (colormap != NULL)	//not entirely sufficient
#endif
		{
setup:
		//FIXME THIS AND/OR THE EXPOSE CB DO NOT WORK PROPERLY
#ifdef USE_GTK2_12
			GtkWindowType wtype;
			g_object_get (G_OBJECT (widget), "type", &wtype, NULL);
			if (wtype == GTK_WINDOW_TOPLEVEL)
				gdk_window_set_opacity (widget->window, gdouble opacity);
			else //<child window
				gdk_window_set_composited (widget->window, TRUE);
#endif
#ifdef USE_GTK2_10
			colormap = gdk_screen_get_rgba_colormap (new_screen);
#endif
			//now we have a colormap appropriate for the screen, use it
			gtk_widget_set_colormap (widget, colormap);
			//we wil draw the background
			gtk_widget_set_app_paintable (widget, TRUE);
			//and this is where we'll do so
	//CHECKME data value
			g_signal_connect (G_OBJECT (widget), "expose-event",
				G_CALLBACK (_e2_window_expose_cb), data);
			if (GTK_WIDGET_VISIBLE (widget))
			{	//trigger a window repaint
				GdkRegion *region = gdk_region_rectangle (&widget->allocation);
				gdk_window_invalidate_region (widget->window, region, TRUE);
				gdk_region_destroy (region);	//CHECKME
			}
		}
#ifndef USE_GTK2_12
	}
#endif
}
*/
#endif //def E2_COMPOSIT

  /*******************/
 /***** 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
*/
static gboolean _e2_window_adjust_pane_ratio_action
	(gpointer from, E2_ActionRuntime *art)
{
	gchar *arg = (gchar *) art->data;
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg);
	return TRUE;
}
/**
@brief respond to pane 1 full-width toggle button

The visible toggle button is swapped, and the panes-ajust fn
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
*/
static gboolean _e2_window_toggle_full_pane1 (gpointer from, E2_ActionRuntime *art)
{
	gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE1FULL])) ?
		"1" : "*";	//no translation
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg);
	return TRUE;
}
/**
@brief respond to pane2 full-width toggle button

The visible toggle button is swapped, and the panes-ajust fn
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
*/
static gboolean _e2_window_toggle_full_pane2 (gpointer from, E2_ActionRuntime *art)
{
	gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_PANE2FULL])) ?
		"0" : "*";	//no translation
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.panes_paned),
		&app.window.panes_paned_pos, &app.window.panes_paned_ratio_last, arg);
	return TRUE;
}
/**
@brief respond to output pane full-window toggle button

The visible toggle button is swapped or changed, and the panes-adjust fn
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
*/
static gboolean _e2_window_toggle_full_output (gpointer from, E2_ActionRuntime *art)
{
	gboolean next_state =
	e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTFULL]);
	gchar *arg = (next_state) ? "0" : "*";

	if (!next_state)
		e2_output_get_bottom_iter (&app.tab);	//get current bottom position

	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
		&app.window.output_paned_pos, &app.window.output_paned_ratio_last, arg);

	if (!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 TRUE;
}
/**
@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
*/
static gboolean _e2_window_toggle_visible_output (gpointer from, E2_ActionRuntime *art)
{
	gchar *arg =
	(e2_toolbar_button_toggle (toggles_array [E2_TOGGLE_OUTPUTSHADE])) ?
		"1" : "*";	//no translation
	_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
		&app.window.output_paned_pos, &app.window.output_paned_ratio_last, arg);
	return TRUE;
}
/**
@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 argument string is valid
*/
static gboolean _e2_window_adjust_output_ratio_action
	(gpointer from, E2_ActionRuntime *art)
{
	gchar *arg = (gchar *) art->data;
	gchar *newarg;
	//check for "1" or "100%" first
	if (strchr (arg, '1') != NULL	//always ascii, no translation
		&& app.window.output_paned_ratio < 1.0)
			newarg = "1";	//no translation
	else if (strchr (arg, '0') != NULL
		&& app.window.output_paned_ratio > 0)
			newarg = "0";	//no translation
	else if (strchr (arg, '*') != NULL)
			newarg = "*";	//no translation
	else
		newarg = NULL;

	if (newarg != NULL)
	{
		gboolean shade = (g_str_equal (newarg,"*")
						&& app.window.output_paned_ratio < 0.01);
		if (shade)
			e2_output_get_bottom_iter (&app.tab);	//get current bottom position

		_e2_window_adjust_panes_ratio (GTK_PANED (app.window.output_paned),
			&app.window.output_paned_pos, &app.window.output_paned_ratio_last, newarg);

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

		return TRUE;
	}
	return FALSE;
}
/**
@brief window-fullscreen toggle action

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

@return TRUE
*/
static gboolean _e2_window_toggle_full_screen (gpointer from, E2_ActionRuntime *art)
{
	if (app.mainwindow_state & GDK_WINDOW_STATE_FULLSCREEN)
		gtk_window_unfullscreen (GTK_WINDOW(app.main_window));
	else
		gtk_window_fullscreen (GTK_WINDOW(app.main_window));
	return TRUE;
}

  /******************/
 /***** 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 widget 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 (app.main_window->window, 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, gchar *arg)
{
	_e2_window_adjust_panes_ratio (GTK_PANED (rt->output_paned),
		&rt->output_paned_pos, &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 (app.status_working)
	{
		printd (DEBUG, "update status bar BLOCKED");
		return TRUE;
	}
#endif
//	printd (DEBUG, "e2_window_update_status_bar");
#ifdef E2_STATUS_BLOCK
	app.status_working = TRUE;
#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
	app.status_working = FALSE;
#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
		app.status_working = FALSE;
#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
		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)
	{
		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 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_role (GTK_WINDOW (app.main_window), "main");
	gtk_window_set_title (GTK_WINDOW (app.main_window), PROGNAME);
	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
	e2_cache_int_register ("window-width", &app.main_window->allocation.width, 640);
	e2_cache_int_register ("window-height", &app.main_window->allocation.height, 480);
	gtk_window_set_default_size (GTK_WINDOW (app.main_window),
		app.main_window->allocation.width, app.main_window->allocation.height);
	gtk_window_set_resizable (GTK_WINDOW (app.main_window), TRUE);
#ifdef E2_COMPOSIT
	e2_window_set_opacity (app.main_window, -1);
#endif
	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_defaults (GTK_BOX (app.hbox_main), app.vbox_main);
	gtk_widget_show (app.hbox_main);
	gtk_widget_show (app.vbox_main);

#ifdef E2_RAINBOW
	e2_option_color_filetypes_sync ();
#endif

	//file panes
	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
	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 ();
	GTK_WIDGET_UNSET_FLAGS (rt->output_paned, GTK_CAN_FOCUS);
	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),
		app.main_window->allocation.height * rt->output_paned_ratio);
	rt->output_paned_pos = gtk_paned_get_position (GTK_PANED (app.window.output_paned));

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

	//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);

	curr_pane = &app.pane1;
	other_pane = &app.pane2;
	curr_view = app.pane1.view = &app.pane1_view;
	other_view = app.pane2.view = &app.pane2_view;

	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 (&_now);
	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);

	//create all bindings, then register _("general"
	//(must be done before file-pane contents are created)
	e2_keybinding_register (_C(17), app.main_window);

	//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);

	//show which pane (pane 1) is active
	e2_pane_flag_active ();
	//setup correct focus
	gtk_widget_grab_focus (curr_view->treeview);
	//other toolbars
	e2_toolbar_initialise (E2_BAR_TASK);
	e2_toolbar_create (&app.toolbar);
	e2_toolbar_initialise (E2_BAR_COMMAND);
	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;
	gint myuid = getuid ();
	GString *who_where = g_string_new ("");
	while ((pw_buf = getpwent ()) != NULL)
		if ((gint) pw_buf->pw_uid == myuid)
			break;
	if (pw_buf != NULL)
		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
	GtkWidget *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);

	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);
}
/**
@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");
	//make sure current cols data are used for the window recreate
	e2_fileview_update_col_cachedata ();

	//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);
	curr_upperx = adj->upper;  //no fn for this
	adj = gtk_scrolled_window_get_hadjustment
		(GTK_SCROLLED_WINDOW (other_pane->pane_sw));
	other_thumbx = gtk_adjustment_get_value (adj);
	other_upperx = adj->upper;  //no fn for this

	LISTS_LOCK
	gboolean busy = curr_view->listcontrols.cd_working
				 || curr_view->listcontrols.refresh_working;
	LISTS_UNLOCK
	if (!busy) //no racing with in-progress re-list
	{
		//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);
	}
	LISTS_LOCK
	busy = other_view->listcontrols.cd_working
		|| other_view->listcontrols.refresh_working;
	LISTS_UNLOCK
	if (!busy)
	{
		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);
	}

	//ensure all toolbar toggle buttons are recreated with their current state
	e2_toolbar_toggle_buttons_set_destroyed (NULL);
	//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);

	//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));

	//prevent any new refresh from starting during this rebuild
	e2_filelist_disable_refresh ();
	//wait until any in-progress refresh is completed
	//CHECKME do this at start of this func ?
	while (TRUE)
	{
		LISTS_LOCK
		busy = curr_view->listcontrols.cd_working
			|| curr_view->listcontrols.refresh_working
			|| other_view->listcontrols.cd_working
			|| other_view->listcontrols.refresh_working;
		LISTS_UNLOCK
		if (!busy)
			break;
		usleep (50000);
	}

	//destroy the widgets
	//toolbar(s) may be outside rt->panes_outer_box, so destroy them independently
	gtk_widget_destroy (app.pane1.toolbar.toolbar_container);
	gtk_widget_destroy (app.pane2.toolbar.toolbar_container);
	gtk_widget_destroy (app.toolbar.toolbar_container);
	gtk_widget_destroy (app.commandbar.toolbar_container);
	gtk_widget_destroy (rt->panes_outer_box);

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

	e2_keybinding_register (_C(17), app.main_window);  //_("general"

	_e2_window_create_pane_boxes (rt);

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

	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_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 ?
	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));
	adj->upper = curr_upperx;
	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

	e2_fileview_scroll_to_position (other_view, 0, other_yscroll);
	adj = gtk_scrolled_window_get_hadjustment
		(GTK_SCROLLED_WINDOW (other_pane->pane_sw));
	adj->upper = other_upperx;
	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);
	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)
{	//(name strings freed as part of the registration process)
	gchar *action_name = g_strconcat(_A(9),".",_A(27),NULL); //"output.adjust_ratio
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_window_adjust_output_ratio_action, &app.window, TRUE);
	action_name = g_strconcat(_A(13),".",_A(27),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_window_adjust_pane_ratio_action, &app.window, TRUE);
	action_name = g_strconcat(_A(15),".",_A(44),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_window_toggle_full_screen, NULL, FALSE);
	action_name = g_strconcat(_A(13),".",_A(70),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_task_refresh, NULL, FALSE);
	action_name = g_strconcat(_A(13),".",_A(72),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_filelist_disable_refresh_action, NULL, FALSE);
	action_name = g_strconcat(_A(13),".",_A(71),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_filelist_enable_refresh_action, NULL, FALSE);
	action_name = g_strconcat(_A(13),".",_A(93),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_task_sync_dirs, NULL, FALSE);
	action_name = g_strconcat(_A(13),".",_A(94),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_window_toggle_panes_direction, NULL, FALSE);
	action_name = g_strconcat(_A(10),".",_A(90),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_pane_activate_other_action, NULL, FALSE);
	//these are "self-managed" toggle actions, with names already logged
	e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_OUTPUTSHADE]),
		E2_ACTION_TYPE_ITEM, _e2_window_toggle_visible_output, NULL, FALSE);
	e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_OUTPUTFULL]),
		E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_output, NULL, FALSE);
 	e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_PANE1FULL]),
		E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_pane1, NULL, FALSE);
 	e2_action_register_simple (g_strdup (toggles_array[E2_TOGGLE_PANE2FULL]),
		E2_ACTION_TYPE_ITEM, _e2_window_toggle_full_pane2, NULL, FALSE);
}
