/* $Id: e2_mkdir_dialog.c 892 2008-05-20 09:16:37Z tpgww $

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

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

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

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

#include "emelfm2.h"
//#include <unistd.h>
#include <string.h>
#include "e2_dialog.h"
#include "e2_mkdir_dialog.h"
#include "e2_task.h"
#include "e2_filelist.h"

//option-pointers shared by all mkdir dialogs in the session
//FIXME make sure these are NULL'd if optionsets are ever renewed
static E2_OptionSet *follow_pane = NULL;
static E2_OptionSet *suggest_dir = NULL;
static E2_OptionSet *show_last = NULL;
static E2_OptionSet *connected = NULL;

static GList *mkdir_history = NULL;

static gboolean _e2_mkdirdlg_change_dir_hook (gchar *path, E2_MkdirDialogRuntime *rt);
static gboolean _e2_mkdirdlg_change_focus_hook (E2_PaneRuntime *pane_rt, E2_MkdirDialogRuntime *rt);
static gboolean _e2_task_mkdirQ (E2_ActionTaskData *qed);

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

/**
@brief determine a suggested directory name, by appending or incrementing a number

@param dir last combo box entry, ie utf8, maybe NULL if no entry
@param parent_dir is rt->path, utf8
@return newly-allocated utf8 string
*/
static gchar *_e2_mkdirdlg_find_dir (const gchar *dir, const gchar *parent_dir)
{
	//we have to suggest something, but if there is nothing
	//in the history yet, we use a default string
	if (dir == NULL)
		dir = _("new directory");

#ifdef E2_VFSTMP
	//FIXME path for non-mounted dirs
#endif
	//the absolute directory name consists of 4 parts:
	//parent directory if the combobox string is not an absolute path
	gchar *part1 = (g_path_is_absolute (dir)) ? "" : (gchar *)parent_dir;
	//part of the combobox string that's a relative path like "bar/" from "bar/foo"
	gchar *part2 = g_path_get_dirname (dir);
	if (g_str_equal (part2, "."))
	{	//no path entered
		g_free (part2);
		part2 = g_strdup ("");
	}
//#warning ignore compiler warning about unitialized usage of digit_end
//#warning ignore compiler warning about unitialized usage of part4
	//the base name of the dir
	gchar *part3 = g_path_get_basename (dir);
	//last part to hold the rest of part 3 after we find a number, if any
	//ignore warning about uninitialized usage
	gchar *part4 = NULL;	//assignment for complier-warning prevention only
	//index (chars, not bytes) of the start of a number in part3
	glong digit_start = -1;
	glong digit_end = 0;	//assignment for complier-warning prevention only
	glong digit_len = 1;
	//digit to increment or add to the directory name
	guint i = 0;
	//complete path (all utf8)
	gchar *path = g_build_filename (part1, part2, part3, NULL);

	gchar *local = F_FILENAME_TO_LOCALE (path);
#ifdef E2_VFS
	VPATH ddata = { local, curr_view->spacedata };
	if (!e2_fs_access (&ddata, F_OK E2_ERR_NONE()))	//through links
#else
	if (!e2_fs_access (local, F_OK E2_ERR_NONE()))	//through links
#endif
	{
		//the directory in the combo entry already exists,
		//check (backwards through the name) whether the name includes a number
		glong j = g_utf8_strlen (part3, -1) - 1;
		gboolean last_was_digit = FALSE;
		gchar *p;
		gunichar c;
		while (j >= 0)
		{
			p = g_utf8_offset_to_pointer (part3, j);
			c = g_utf8_get_char (p);
			if (g_unichar_isdigit (c))
			{
				if (!last_was_digit)
				{
					last_was_digit = TRUE;
					digit_end = j;
				}
//				digit_start = j;
				if (j == 0)	//no chance of a preceeding non-digit
					digit_start = 0;
			}
			else if (last_was_digit)
			{
				digit_start = j + 1;
				break;
			}
			j--;
		}
		if (digit_start != -1)
		{
			//there is a number, grab it and otherwise carve up the entered name
			digit_len = digit_end - digit_start + 1;
			p = g_utf8_offset_to_pointer (part3, digit_end + 1);
			part4 = g_strdup (p);
			*p ='\0';
			p = g_utf8_offset_to_pointer (part3, digit_start);
			i = (guint) atoi (p);
			*p = '\0';
		}

		gchar numfmt[20];
		numfmt[0] = '%';
		if (digit_len > 1)
			g_snprintf (numfmt+1, sizeof(numfmt)-1, "0%luu", digit_len);
		else
			g_strlcpy (numfmt+1, "u", sizeof(numfmt)-1);
		//if there is no number in the directory string, we simply add it
		//to the end
		gchar *freeme;
		gchar *format = g_strconcat (
				"%s", //(*part1 == '\0') ? "" : G_DIR_SEPARATOR_S,
				"%s", (*part2 == '\0') ? "" : G_DIR_SEPARATOR_S,
				"%s",	//part3 always has something useful
				 numfmt, NULL);
		//otherwise, add a trailing token for part4
		if (digit_start != -1)
		{
			freeme = format;
			format = g_strconcat (freeme, "%s", NULL);
			g_free (freeme);
		}
		//now check for a non-existent item with an incremented number
#ifdef E2_VFS
		while (!e2_fs_access (&ddata, F_OK E2_ERR_NONE()))
#else
		while (!e2_fs_access (local, F_OK E2_ERR_NONE()))
#endif
		{
			i++;
			g_free (path);
			if (digit_start != -1)
				path = g_strdup_printf (format, part1, part2, part3, i, part4);
			else
				path = g_strdup_printf (format, part1, part2, part3, i);
			F_FREE (local);
			local = F_FILENAME_TO_LOCALE (path);
		}
		g_free (format);
		g_free (path);
		F_FREE (local);

		//now build the string for the entry
		//from the the string the user really entered
		if (digit_start != -1)
		{
			format = g_strconcat ("%s%s", numfmt, "%s", NULL);
			path = g_strdup_printf (format, part2, part3, i, part4);
		}
		else
		{
			format = g_strconcat ("%s%s", numfmt, NULL);
			path = g_strdup_printf (format, part2, part3, i);
		}
		g_free (format);
	}
	else
	{
		g_free (path);
		path = g_strdup (dir);
	}
	//cleanup
	g_free (part2);
	g_free (part3);

	return path;
}
/**
@brief walk up the path of @a parent until an existing directory is found

@param parent path of dir to check, freeable utf8 string
@param exists store for flag indicating that directory @a parent was not found
@param i store for no. of path segments scanned
@return utf8 string, @a parent or newly-allocated substitute
*/
static gchar *_e2_mkdirdlg_real_parent (gchar *parent, gboolean *exists, gint *i)
{
#ifdef E2_VFSTMP
//FIXME handle vfs parent
#endif
	gchar *local = F_FILENAME_TO_LOCALE (parent);
#ifdef E2_VFS
	VPATH ddata = { local, curr_view->spacedata };
	while (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || ! e2_fs_is_dir3 (&ddata E2_ERR_NONE()))
#else
	while (e2_fs_access (local, F_OK E2_ERR_NONE()) || ! e2_fs_is_dir3 (local E2_ERR_NONE()))
#endif
	{
		(*i)++;
		*exists = FALSE;
		if ((parent[0] == '/') && (parent[1] == '\0'))
			break;
		gchar *freeme = parent;
		parent = g_path_get_dirname (parent);
		F_FREE (local);
		local = F_FILENAME_TO_LOCALE (parent);
		g_free (freeme);
	}
	F_FREE (local);
	return parent;
}
/**
@brief clean up after a mkdir dialog
This is called only from within the dialog response callback (so BGL applies)
@param rt pointer to dialog data struct
@return
*/
static void _e2_mkdirdlg_dialog_destroy (E2_MkdirDialogRuntime *rt)
{
//#ifdef RACE_CHECK
	printd (DEBUG, "mkdir dialog destroy");
//#endif
	gtk_widget_destroy (rt->dialog);
	e2_list_free_with_data (&rt->history);
	e2_hook_unregister (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE);
	e2_hook_unregister (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE);
	e2_hook_unregister (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt, TRUE);
//#ifdef RACE_CHECK
//	printd (DEBUG, "mkdir dialog unhooks completed");
//#endif
	g_free (rt->path);
	if (rt->idle_id != 0)
#ifdef RACE_CHECK
	{
		printd (DEBUG, "mkdir dialog");
		gboolean debug =
#endif
		g_source_remove (rt->idle_id);
#ifdef RACE_CHECK
		if (debug)
			printd (DEBUG, " idle removal completed");
		else
			printd (DEBUG, " idle removal failed");
	}
//	else
//		printd (DEBUG, "mkdir dialog NO idle source to remove");
#endif
	DEALLOCATE (E2_MkdirDialogRuntime, rt);
//#ifdef RACE_CHECK
//	printd (DEBUG, "mkdir dialog destroy COMPLETED");
//#endif
	gtk_main_quit ();
}
/**
@brief change size of a mkdir dialog window
This is called from several placces, all inside BGL
@param rt pointer to dialog data struct
@return
*/
static void _e2_mkdirdlg_update_dialog_size (E2_MkdirDialogRuntime *rt)
{
//	printd (DEBUG, "mkdir dialog resize pending");
	e2_dialog_resize_with_sw (rt->dialog, rt->scrolled);	//this works at idle-time
}
/**
@brief update dialog label which describes the parent path
This is called from _e2_mkdirdlg_update_status() (inside BGL)
@param path parent directory path, utf8 string
@param rt pointer to dialog data struct
@return
*/
static void _e2_mkdirdlg_update_parent_label (gchar *path, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog update parent label");
#ifdef E2_VFSTMP
	//FIXME path for non-mounted dirs
#else
	gchar *translated = e2_utils_translate_relative_path (rt->path, path);
#endif
	gchar *public = g_markup_escape_text (translated, -1);
	gchar *label = g_strconcat ("<small>", public, "</small>", NULL);
	gtk_label_set_markup (GTK_LABEL (rt->info_label), label);
	g_free (translated);
	g_free (public);
	g_free (label);
}
/**
@brief update dialog label which describes creation, and also buttons' sensitivity
This is called from _e2_mkdirdlg_update_status() (inside BGL)
@param reason explanation string
@param rt pointer to dialog data struct
@return
*/
static void _e2_mkdirdlg_update_creation_widgets (gchar *reason, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog update creation widgets");
	gchar *color;
	gchar *constant;
	if (rt->creation_possible)
	{
		//FIXME gtk stops repeated clicking of create button without leaving it
		//if the E2_RESPONSE_CREATE sensitivity or corresponding button
		//sensitivity is ever changed either way
		gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_CREATE, TRUE);
		gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, TRUE);
		color = e2_option_str_get ("color-positive");
		constant = _("yes");
	}
	else
	{
		gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_CREATE, FALSE);
		gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, FALSE);
		color = e2_option_str_get ("color-negative");
		constant = _("no");
	}
	gchar *label = g_strconcat ("<span weight=\"bold\" size=\"small\" foreground=\"",
			color, "\">", constant, " </span>", reason == NULL ? NULL : "<small>(",
			reason, ")</small>", NULL);
	gtk_label_set_markup (GTK_LABEL (rt->info_label2), label);
	g_free (label);
}
/**
@brief update the two info labels in the info frame, and buttons' sensitivity
This is done even if the info frame isn't shown, to update the
rt->creation_possible state and buttons
It must be called only from inside BGL as no thread protection is done
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_update_status (E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog update status");
	gboolean absolute;
	gchar *path, *parent;
	E2_ERR_DECLARE

	rt->creation_possible = FALSE;

	const gchar *entry_text = gtk_entry_get_text
		(GTK_ENTRY (GTK_BIN (rt->combo)->child));
	//if the entry is empty, we'll only check if the parent directory
	//is writable, so, set path to the currently active directory
	if (*entry_text == '\0')
	{
		path = g_strdup (rt->path);
		absolute = TRUE;	//CHECKME
		parent = g_strdup (path);
	}
	else //otherwise we have to create an absolute path from
			//the entry text and the active directory
	{
		path = (gchar *) entry_text;
		absolute = g_path_is_absolute (path);
		if (absolute)
			path = g_strdup (entry_text); //make sure this is always freeable
		else
			//the directory entered is not absolute, prepend ...
			path = g_strconcat (rt->path, path, NULL);  //CHECKME extra separator ?
		parent = g_path_get_dirname (path);
	}

	gboolean parent_exists = TRUE;
	//number of "ancestor" dirs that need to be created
	gint num = g_str_has_suffix (entry_text, G_DIR_SEPARATOR_S) ? 0 : 1;
	parent = _e2_mkdirdlg_real_parent (parent, &parent_exists, &num);

	_e2_mkdirdlg_update_parent_label (parent, rt);

	gchar *reason = NULL;
	if (*entry_text == '\0')
	{
		_e2_mkdirdlg_update_creation_widgets (reason, rt);
		g_free (path);
		g_free (parent);
		return;
	}
	gchar *local = F_FILENAME_TO_LOCALE (parent);
#ifdef E2_VFS
	VPATH ddata = { local, curr_view->spacedata };
	if ((e2_fs_access (&ddata, W_OK | X_OK E2_ERR_PTR())) != 0)
#else
	if ((e2_fs_access (local, W_OK | X_OK E2_ERR_PTR())) != 0)
#endif
	{
		if (absolute)
			reason = g_strdup_printf (_("cannot write to '%s' - %s"), parent,
#ifdef E2_VFS
			E2_ERR_NAME->message
#else
			g_strerror (errno)
#endif
		);
		else if (parent_exists)
			reason = g_strdup_printf (_("cannot write to parent directory - %s"),
#ifdef E2_VFS
				E2_ERR_NAME->message
#else
				g_strerror (errno)
#endif
		);
		else
			reason = g_strdup_printf (_("only '%s' exists - %s"), parent,
#ifdef E2_VFS
				E2_ERR_NAME->message
#else
				g_strerror (errno)
#endif
		);
		_e2_mkdirdlg_update_creation_widgets (reason, rt);

		E2_ERR_CLEAR
		g_free (path);
		g_free (parent);
		F_FREE (local);
		return;
	}
	F_FREE (local);
	local = F_FILENAME_TO_LOCALE (path);
#ifdef E2_VFS
	ddata.localpath = local;
	if (!e2_fs_access (&ddata, F_OK E2_ERR_NONE()))
#else
	if (!e2_fs_access (local, F_OK E2_ERR_NONE()))
#endif
	{
#ifdef E2_VFS
		if  (e2_fs_is_dir3 (&ddata E2_ERR_NONE()))
#else
		if  (e2_fs_is_dir3 (local E2_ERR_NONE()))
#endif
			reason = _("the directory already exists.");
		else
			reason = _("something is in the way.");
		_e2_mkdirdlg_update_creation_widgets (reason, rt);
		g_free (path);
		g_free (parent);
		F_FREE (local);
		return;
	}
	F_FREE (local);

	if (num > 1)
		reason = g_strdup_printf ("%d %s",num,_("directories will be created"));
	rt->creation_possible = TRUE;
	_e2_mkdirdlg_update_creation_widgets (reason, rt);
	g_free (parent);
	g_free (path);
}
/**
@brief update suggested name for the next mkdir, and consequent UI changes
This is called from several places, all with BGL closed
@param rt pointer to dialog data struct
@return
*/
static void _e2_mkdirdlg_update_name_and_status (E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog update name and status");
	GtkWidget *entry = GTK_BIN (rt->combo)->child;
	if (rt->opt_suggest_dir || rt->opt_show_last)
	{
		gchar *text = e2_combobox_first_text (GTK_COMBO_BOX (rt->combo));
		if (text != NULL)
		{
			if (rt->opt_suggest_dir)
			{
				gchar *new_text = _e2_mkdirdlg_find_dir (text, rt->path);
				gtk_entry_set_text (GTK_ENTRY (entry), new_text);
				g_free (new_text);
			}
			g_free (text);
			gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
		}
		else
			gtk_entry_set_text (GTK_ENTRY (entry), "");
	}
	else
		gtk_entry_set_text (GTK_ENTRY (entry), "");

	_e2_mkdirdlg_update_status (rt);

#ifdef RACE_CHECK
	printd (DEBUG, "mkdir update combo completed");
#endif
}
/**
@brief set or clear mechanism for following changes of active directory
This is called out of BGL
If following is turned on, the default path is updated and status info etc
updated accordingly
@param rt pointer to dialog data struct
@param follow TRUE to turn on following, FALSE to turn it off
@return
*/
static void _e2_mkdirdlg_update_follow_dir (E2_MkdirDialogRuntime *rt, gboolean follow)
{
	printd (DEBUG, "mkdir dialog update dir-following");
	if (follow)
	{
		g_free (rt->path);
		rt->path = g_strdup (curr_pane->path);
		gdk_threads_enter ();
		_e2_mkdirdlg_update_status (rt);
		gdk_threads_leave ();
#ifdef E2_VFSTMP
	//CHECKME hooks for non-mounted dirs
#endif
		e2_hook_register (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt);
		e2_hook_register (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt);
		e2_hook_register (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt);
	}
	else
	{
		e2_hook_unregister (&app.pane1.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE);
		e2_hook_unregister (&app.pane2.hook_change_dir, _e2_mkdirdlg_change_dir_hook, rt, TRUE);
		e2_hook_unregister (&app.hook_pane_focus_changed, _e2_mkdirdlg_change_focus_hook, rt, TRUE);
	}
}
/**
@brief hook function for app.paneX.hook_change_dir
This is called with BGL off/open
@param path path of an opened directory, utf-8 string
@param rt pointer to dialog's data struct
@return TRUE
*/
static gboolean _e2_mkdirdlg_change_dir_hook (gchar *path, E2_MkdirDialogRuntime *rt)
{
	g_free (rt->path);
	rt->path = g_strdup (path);
	gdk_threads_enter ();
	_e2_mkdirdlg_update_status (rt);
	_e2_mkdirdlg_update_dialog_size (rt);
	gdk_threads_leave ();
	return TRUE;
}
/**
@brief hook function for app.hook_pane_focus_changed
This is called with BGL off/open
@param pane_rt data struct for the currenly-focused pane
@param rt pointer to dialog's data struct
@return TRUE
*/
static gboolean _e2_mkdirdlg_change_focus_hook (E2_PaneRuntime *pane_rt, E2_MkdirDialogRuntime *rt)
{
	g_free (rt->path);
#ifdef E2_VFSTMP
	//FIXME path for non-mounted dirs
#else
	LISTS_LOCK
	rt->path = g_strdup (pane_rt->path);
	LISTS_UNLOCK
#endif
	gdk_threads_enter ();
	_e2_mkdirdlg_update_status (rt);
	_e2_mkdirdlg_update_dialog_size (rt);
	gdk_threads_leave ();
	return TRUE;
}
/**
@brief create a new directory, along with any missing "ancestor(s)", in the active-pane dir
The string provided may be an absolute or relative path, the latter
including just a name. If relative, the last-used dir?? will be prepended
This is called only from _e2_mkdirdlg_response_cb () with BGL closed
@param entry the entry widget for the name-entry combo
@param rt pointer to dialog's data struct
@param close TRUE if the dialog will be closed when finished here

@return
*/
static void _e2_mkdirdlg_create_dir (GtkWidget *entry, E2_MkdirDialogRuntime *rt,
	gboolean close)
{
	printd (DEBUG, "mkdir dialog create dir");
	if (!rt->creation_possible)
		return;
	const gchar *dir = gtk_entry_get_text (GTK_ENTRY (entry));
	//quick exit
	if (*dir == '\0')
		return;

	gchar *path, *parent, *local, *freeme = NULL;
	if (g_path_is_absolute (dir))
		path = g_strdup (dir);
	else
		path = g_strconcat (rt->path, dir, NULL);

	//find closest existing "ancestor" of the dir to be created, and any
	//intervening ancestor dir(s) that need to be created
	GList *tmp, *missing = NULL;
	parent = g_path_get_dirname (path);
	if (g_str_has_suffix (path, G_DIR_SEPARATOR_S))
	{
		freeme = parent;
		parent = g_path_get_dirname (parent);
		g_free (freeme);
	}
	freeme = local = F_FILENAME_TO_LOCALE (parent);
#ifdef E2_VFS
	VPATH ddata = { local, curr_view->spacedata };
	while (e2_fs_access (&ddata, F_OK E2_ERR_NONE()) || !e2_fs_is_dir3 (&ddata E2_ERR_NONE()))
#else
	while (e2_fs_access (local, F_OK E2_ERR_NONE()) || !e2_fs_is_dir3 (local E2_ERR_NONE()))
#endif
	{
#ifdef E2_VFSTMP
	//FIXME parent path for virtual dirs
#endif
		if ((parent[0] == G_DIR_SEPARATOR) && (parent[1] == '\0'))
			break;
		missing = g_list_prepend (missing, parent);
		parent = g_path_get_dirname (parent);
		F_FREE (local);
		local = F_FILENAME_TO_LOCALE (parent);
	}
	g_free (parent);
	F_FREE (freeme);

	E2_ERR_DECLARE
	//create any missing ancestor(s)
	for (tmp = missing; tmp != NULL; tmp = g_list_next (tmp))
	{
		local = F_FILENAME_TO_LOCALE ((gchar *)tmp->data);
//		E2_ERR_NAME = NULL;
#ifdef E2_VFS
		ddata.localpath = local;
		if (e2_fs_mkdir (&ddata, 0777 E2_ERR_PTR()))	//FIXME vfs
#else
		if (e2_fs_mkdir (local, 0777 E2_ERR_PTR()))	//FIXME vfs
#endif
		{
			gdk_threads_leave (); //downstream does local mutex management
			e2_fs_error_local (_("Cannot create directory %s"),
#ifdef E2_VFS
				&ddata E2_ERR_MSGL());
#else
				local E2_ERR_MSGL());
#endif
			gdk_threads_enter ();
			E2_ERR_CLEAR

			if (!close)
				_e2_mkdirdlg_update_status (rt);
#ifdef E2_VFSTMP
			//FIXME path for non-mounted dirs
#else
			e2_filelist_request_refresh (rt->path, TRUE);
#endif
			F_FREE (local);
			e2_list_free_with_data (&missing);
			return;
		}
		F_FREE (local);
	}
	e2_list_free_with_data (&missing);

	//now create the chosen dir itself
	local = F_FILENAME_TO_LOCALE (path);
#ifdef E2_VFS
	ddata.localpath = local;
	if (!e2_fs_mkdir (&ddata, 0777 E2_ERR_PTR()))
#else
	if (!e2_fs_mkdir (local, 0777 E2_ERR_PTR()))
#endif
	{	//succeeded
		if (!close)
			_e2_mkdirdlg_update_name_and_status (rt);
#ifdef E2_VFSTMP
	//FIXME path for non-mounted dirs
#else
		e2_filelist_request_refresh (rt->path, TRUE);
#endif
		e2_list_update_history ((gchar *) dir, &mkdir_history, NULL, 30, FALSE);
	}
	else	//mkdir failed
	{
		gdk_threads_leave (); //downstream does local mutex management
		e2_fs_error_local (_("Cannot create directory %s"),
#ifdef E2_VFS
			&ddata E2_ERR_MSGL());
#else
			local E2_ERR_MSGL());
#endif
		gdk_threads_enter ();
		E2_ERR_CLEAR

		if (!close)
			_e2_mkdirdlg_update_status (rt);
	}
	g_free (path);
	F_FREE (local);
}

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

/**
@brief dialog response callback

@param dialog the dialog where the response was initiated
@param response the response assigned to the activated button
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_response_cb (GtkDialog *dialog, gint response,
	E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog response_cb (dialog:_,response:%d,rt:_)", response);
	GtkWidget *entry;
	switch (response)
	{
		case E2_RESPONSE_CREATE:
		case GTK_RESPONSE_OK:
			entry = GTK_BIN (rt->combo)->child;
			const gchar *dir = gtk_entry_get_text (GTK_ENTRY (entry));
			//quick exit
			if (*dir == '\0')
				break;
			if (strchr (dir, '%') != NULL)
			{
				gchar *freeme = e2_utils_expand_macros ((gchar *)dir, NULL);
				if (freeme != NULL && freeme != GINT_TO_POINTER (1))
				{
					gtk_entry_set_text (GTK_ENTRY (entry), freeme);
					g_free (freeme);
				}
			}
			e2_combobox_activated_cb (entry, GINT_TO_POINTER (FALSE));
			*rt->status = E2_TASK_RUNNING;
			if (response == E2_RESPONSE_CREATE)
			{
				_e2_mkdirdlg_create_dir (entry, rt, FALSE);
//				e2_filelist_request_focus (entry);
				*rt->status = E2_TASK_PAUSED;
				gtk_widget_grab_focus (entry);
				break;
			}
			_e2_mkdirdlg_create_dir (entry, rt, TRUE);
		default:
			_e2_mkdirdlg_dialog_destroy (rt);
			break;
	}
}
/**
@brief combobox active-item "changed" callback
This is also called every time a letter is added to or removed from the
dialog's name-entry
@param combo UNUSED the affected combobox
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_changed_cb (GtkComboBox *combo, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog changed cb");
	_e2_mkdirdlg_update_status (rt);
	_e2_mkdirdlg_update_dialog_size (rt);
}
/**
@brief combobox entry "activate" callback
This simply triggers an 'ok' type response
@param entry UNUSED the entry widget for the name-entry combo
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_activate_cb (GtkWidget *entry, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog activate cb");
	_e2_mkdirdlg_response_cb (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, rt);
}
/**
@brief callback for context menu item for toggling "follow active pane"
@param menu_item the activated widget
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_toggled1_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog follow pane toggle");
	gboolean state = gtk_check_menu_item_get_active
		(GTK_CHECK_MENU_ITEM (menu_item));
	//open BGL to allow downstream mutex management
	gdk_threads_leave ();
	_e2_mkdirdlg_update_follow_dir (rt, state);
	gdk_threads_enter ();
	gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked");
	if (!GPOINTER_TO_INT (p))
		e2_option_bool_set_direct (follow_pane, state);
}
/**
@brief callback for context menu item for toggling "show last entry"
@param menu_item the activated widget
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_toggled2_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog show last entry toggle");
	rt->opt_show_last = gtk_check_menu_item_get_active
		(GTK_CHECK_MENU_ITEM (menu_item));
	_e2_mkdirdlg_update_name_and_status (rt);
	gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked");
	if (!GPOINTER_TO_INT (p))
		e2_option_bool_set_direct (show_last, rt->opt_show_last);
}
/**
@brief callback for context menu item for toggling "suggest dir"
@param menu_item the activated widget
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_toggled3_cb (GtkWidget *menu_item, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog suggest dir toggle");
	rt->opt_suggest_dir = gtk_check_menu_item_get_active
		(GTK_CHECK_MENU_ITEM (menu_item));
	_e2_mkdirdlg_update_name_and_status (rt);
	gpointer p = g_object_get_data (G_OBJECT (rt->dialog), "e2-controller-blocked");
	if (!GPOINTER_TO_INT (p))
		e2_option_bool_set_direct (suggest_dir, rt->opt_suggest_dir);
}
/**
@brief callback for info expander opened or closed
@param expander the activated widget
@param spec
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_expander_toggled_cb (GtkWidget *expander,
	GParamSpec *spec, E2_MkdirDialogRuntime *rt)
{
//	printd (DEBUG, "mkdir dialog expander toggle");
	_e2_mkdirdlg_update_dialog_size (rt);
}
/**
@brief callback for dialog window shown
@param dialog the shown widget
@param rt pointer to dialog's data struct
@return
*/
static void _e2_mkdirdlg_show_cb (GtkWidget *dialog, E2_MkdirDialogRuntime *rt)
{
	printd (DEBUG, "mkdir dialog show cb");
	//convenient to set name here, inside BGL
	_e2_mkdirdlg_update_name_and_status (rt);
	gtk_expander_set_expanded (GTK_EXPANDER (rt->info_expander),
		e2_option_bool_get ("dialog-mkdir-show-info"));
}

  /******************/
 /***** action *****/
/******************/
/**
@brief create and show a mkdir dialog
@return TRUE always
*/
static gboolean _e2_mkdir_dialog_create (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_MKDIR, art, from,
		_e2_task_mkdirQ, NULL));
}
static gboolean _e2_task_mkdirQ (E2_ActionTaskData *qed)
{
	//init runtime object
	E2_MkdirDialogRuntime *rt = ALLOCATE (E2_MkdirDialogRuntime);
	CHECKALLOCATEDWARN (rt, return FALSE;)
	rt->history = e2_list_copy_with_data (mkdir_history);
#ifdef E2_VFSTMP
	//FIXME path for non-mounted dirs
#else
	rt->path = D_FILENAME_FROM_LOCALE (qed->currdir);
#endif
	rt->status = qed->status;	//enable on-the-fly status changes
	rt->creation_possible = TRUE;
	rt->idle_id = 0;

	//create dialog
	rt->dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION,
		_("What is the new directory's name?"), _("create directory"),
		_e2_mkdirdlg_response_cb, rt);

	GtkWidget *vbox = e2_dialog_add_sw (rt->dialog);
	rt->scrolled = vbox->parent->parent;	//bad !!

	//CHECKME this is needed to workaround gtk weirdness,
	//= bad delay when any key is pressed during a cursor-visible interval
	gdk_threads_enter ();
	//add combo
	rt->combo = e2_combobox_add (vbox, FALSE, E2_PADDING,
		_e2_mkdirdlg_activate_cb, rt, &mkdir_history,
		E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE);
	gdk_threads_leave ();
	//add expander with info box as its child
	GtkWidget *info_box = e2_widget_get_box (TRUE, FALSE, 1);
	rt->info_expander = e2_widget_add_expander (vbox, _("info"),
		e2_option_get ("dialog-mkdir-show-info"), rt->dialog, info_box);

	g_signal_connect (G_OBJECT (rt->info_expander), "notify::expanded",
		G_CALLBACK (_e2_mkdirdlg_expander_toggled_cb), rt);

	GtkSizeGroup *group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
	GtkWidget *hbox = e2_widget_add_box (info_box, FALSE, 0, FALSE, FALSE, 0);
	GtkWidget *label = e2_widget_add_label (hbox,
		_("<small>parent directory:</small>"), 0.0, 0.0, FALSE, E2_PADDING_SMALL);
	gtk_size_group_add_widget (group, label);

	rt->info_label = e2_widget_add_mid_label (hbox, "", 0.0, TRUE, E2_PADDING_SMALL);
	hbox = e2_widget_add_box (info_box, FALSE, 0, FALSE, FALSE, 0);
	label = e2_widget_add_label (hbox,
		_("<small>creation possible:</small>"), 0.0, 0.0, FALSE, E2_PADDING_SMALL);
	gtk_size_group_add_widget (group, label);
	rt->info_label2 = e2_widget_add_label (hbox, "", 0.0, 0.0, TRUE, E2_PADDING_SMALL);
	g_object_unref (G_OBJECT (group));

	if (follow_pane == NULL)
		follow_pane = e2_option_get ("dialog-mkdir-follow-pane");
	if (suggest_dir == NULL)
		suggest_dir = e2_option_get ("dialog-mkdir-suggest-directory");
	if (show_last == NULL)
		show_last = e2_option_get ("dialog-mkdir-show-last");
	if (connected == NULL)
		connected = e2_option_get ("dialog-mkdir-connected");

	rt->menu = e2_menu_create_options_menu (rt->dialog, NULL,
		follow_pane, _e2_mkdirdlg_toggled1_cb, rt,
		suggest_dir, _e2_mkdirdlg_toggled3_cb, rt,
		show_last, _e2_mkdirdlg_toggled2_cb, rt,
		connected, e2_menu_control_cb, rt->dialog,
		NULL);
	e2_dialog_attach_menu (rt->dialog, rt->menu);

//do this after create button is created
	_e2_mkdirdlg_update_follow_dir (rt, e2_option_bool_get_direct (follow_pane));
	e2_option_connect (rt->dialog, e2_option_bool_get_direct (connected));
	rt->opt_show_last = e2_option_bool_get_direct (show_last);
	rt->opt_suggest_dir = e2_option_bool_get_direct (suggest_dir);

	g_signal_connect (G_OBJECT (rt->dialog), "show",
		G_CALLBACK (_e2_mkdirdlg_show_cb), rt);

	E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
	E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;

//	e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CANCEL);
//	rt->create_btn =
//	e2_dialog_add_defined_button (rt->dialog, &E2_BUTTON_CREATE);

//	_e2_mkdirdlg_update_follow_dir (rt, e2_option_bool_get_direct (follow_pane));

    e2_dialog_show (rt->dialog, app.main_window, E2_DIALOG_THREADED | E2_DIALOG_DONT_SHOW_ALL,
		&E2_BUTTON_CANCEL, &E2_BUTTON_CREATE, &E2_BUTTON_OK, NULL);
	//do this after showing, to reduce repitition
	g_signal_connect (G_OBJECT (rt->combo), "changed",
		G_CALLBACK (_e2_mkdirdlg_changed_cb), rt);

	*qed->status = E2_TASK_PAUSED;
	gdk_threads_enter ();
	gtk_main ();	//block the queue-thread until the user is finished
	gdk_threads_leave ();
	return TRUE;
}

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

void e2_mkdir_dialog_actions_register ()
{
	e2_cache_list_register ("mkdir-history", &mkdir_history);

	gchar *action_name = g_strconcat(_A(1),".",_A(56),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_mkdir_dialog_create, NULL, FALSE);
}

void e2_mkdir_dialog_options_register ()
{
	gchar* group_name = g_strconcat(_C(11),":",_C(23),NULL);   //_("dialogs:mkdir"
	e2_option_bool_register ("dialog-mkdir-show-info",
		group_name, _("open info frame"),
		_("This causes make-directory dialogs to start with extra information displayed"), NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("dialog-mkdir-follow-pane",
		group_name, _("follow active-pane directory"),
		_("This makes the parent directory for new directories the same as the one in the active pane, even if the latter changes"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("dialog-mkdir-suggest-directory",
		group_name, _("suggest directory name"),
		_("This presents a suggested name for each new directory, based on the last-created directory with an increasing number appended"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("dialog-mkdir-show-last",
		group_name, _("show last directory name"),
		_("This causes the name of the last-created directory to be shown in the entry field, after opening the dialog or when creating another directory"),
			"!dialog-mkdir-suggest-directory", FALSE,
		E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("dialog-mkdir-connected", group_name, _("replicate changes"),
		_("This causes option-changes to be replicated in other mkdir dialogs. Otherwise such changes will be confined to the current dialog"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
}
