/* $Id: e2_dialog.c 838 2008-03-23 22:25:52Z tpgww $

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

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

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

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

/**
@file src/dialogs/e2_dialog.c
@brief dialog utility functions

This file contains dialog utility functions.
*/
/**
\page dialogs dialogs

ToDo - describe how dialogs work
*/

#include "emelfm2.h"
#include "e2_dialog.h"
#include "e2_option.h"
#include "e2_filelist.h"
#include <string.h>

static DialogButtons _e2_dialog_line_input (gchar* window_title, gchar *prompt,
	gchar *suggestion, gboolean select_text, //tag PASSWORDINPUT gboolean hide_text,
	gchar **input, OW_ButtonFlags extras, gboolean history, GList **history_list);

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

/**
@brief remove the source for a pending dialog resize
@param dialog the affected dialog widget
@param data UNUSED data specified when the idle was established
@return
*/
static void _e2_dialog_resize_destroy_cb (GtkWidget *dialog, gpointer data)
{
	gpointer idle_id = g_object_get_data (G_OBJECT (dialog),
			"e2-dialog-resize-idle-id");
	if (idle_id != NULL)
	{
		g_source_remove (GPOINTER_TO_UINT(idle_id));
		g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id", NULL);
	}
}
/**
@brief idle callback to resize @a dialog
@param dialog the widget to be resized
@return FALSE always, to remove the idle source
*/
static gboolean _e2_dialog_resize_idle_cb (GtkWidget *dialog)
{
	if ((dialog->allocation.width != dialog->requisition.width) ||
		(dialog->allocation.height != dialog->requisition.height))
	{
//		GtkPolicyType barpolicy = GTK_POLICY_NEVER;
		GtkScrolledWindow *scrolled = g_object_get_data (G_OBJECT (dialog),
			"e2-dialog-resize-scrolled");
		gdk_threads_enter ();
		if (scrolled != NULL)
		{
			//turn any scrollbar(s) off, so the resize works as intended
			//assume both bars have same policy
//			barpolicy = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (scrolled), "hscrollbar-policy"));
			gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
				GTK_POLICY_NEVER, GTK_POLICY_NEVER);
			WAIT_FOR_EVENTS
		}
		gtk_window_resize (GTK_WINDOW (dialog),
			dialog->requisition.width, dialog->requisition.height);
		WAIT_FOR_EVENTS
		//turn bars back on
		if (scrolled != NULL)	//&& barpolicy != GTK_POLICY_NEVER)
		{
			gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
				GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);	//barpolicy, barpolicy);
		}
		gdk_threads_leave ();
	}
	//record 0 idle id, we're done now
	g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id", NULL);
	return FALSE;
}
/**
@brief "show" callback for a dialog with scrolled window
Turns on automatic scrollbars for @a scrolled
@param dialog UNUSED the dialog widget containing @a scrolled
@param scrolled scrolled window to be updated
@return
*/
void e2_dialog_show_cb (GtkWidget *dialog, GtkScrolledWindow *scrolled)
{
	gtk_scrolled_window_set_policy (scrolled, GTK_POLICY_AUTOMATIC,
		GTK_POLICY_AUTOMATIC);
}
/**
@brief "show" callback for a dialog with notebook
Turns on automatic scrollbars for each page of @a book
@param dialog UNUSED the dialog widget containing @a book
@param book notebook to be updated
@return
*/
void e2_dialog_show_notebook_cb (GtkWidget *dialog, GtkNotebook *book)
{
	gint i, pagecount = gtk_notebook_get_n_pages (book);
	for (i = 0; i < pagecount; i++)
	{
		GtkWidget *page;
		GtkScrolledWindow *sw;
		page = gtk_notebook_get_nth_page (book, i);
		sw = (GtkScrolledWindow *)g_object_get_data (G_OBJECT (page),
			"e2-tab-scrolled-window");
		if (sw != NULL)
			gtk_scrolled_window_set_policy (sw, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	}
}
/**
@brief dialog button-press callback, to pop up a context menu
@param dialog the widget which received the mouse button press
@param event pointer to event data struct
@param menu pointer to context menu for the dialog
@return TRUE if the event was a right-button click
*/
static gboolean _e2_dialog_button_press_cb (GtkWidget *dialog,
	GdkEventButton *event, GtkWidget *menu)
{
	if (event->button == 3)
	{
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time);
		return TRUE;
	}
	return FALSE;
}
/**
@brief handle Return keypresses in a line-input dialog
This is convenient because the buttons in the dialog are
typically not focused
@param entry the entry widget for the combo box
@param result pointer to store for result code
@return
*/
static void _e2_dialog_activated_cb (GtkEntry *entry,
	DialogButtons *dialog_result)
{
	*dialog_result = OK;
	gtk_main_quit ();
}
/**
@brief default callback to handle responses from @a dialog
This is essentially to handle cancellation requests
@param dialog the dialog where the response was triggered
@param response the response enumerator
@param data UNUSED data specified when the callback was connected
@return
*/
static void _e2_dialog_response_cb (GtkDialog *dialog, gint response,
	gpointer data)
{
	gint negresponse = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog),
		"e2-negative-response"));
	if (response == negresponse)	// || response == GTK_RESPONSE_DELETE_EVENT)
		e2_dialog_cancel_cb (NULL, GTK_WIDGET (dialog));
}

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

/**
@brief convert a dialog button response to the interface standard
This expects a dialog paused with gtk_main()
@param dialog UNUSED the dialog where the response was generated
@param response the generated response number
@param dialog_result ptr to store for the decoded result
@return
*/
void e2_dialog_response_decode_cb (GtkDialog *dialog, gint response,
	DialogButtons *dialog_result)
{
	switch (response)
	{
		case GTK_RESPONSE_OK:
		case GTK_RESPONSE_YES:
		case GTK_RESPONSE_APPLY:
			*dialog_result = OK;
			break;
		case GTK_RESPONSE_CANCEL:
		case GTK_RESPONSE_NO:
			*dialog_result = CANCEL;
			break;
		case E2_RESPONSE_YESTOALL:
		case E2_RESPONSE_APPLYTOALL:
			*dialog_result = YES_TO_ALL;
			break;
//		case E2_RESPONSE_NOTOALL:
		default:
			*dialog_result = NO_TO_ALL;
			break;
	}

	gtk_main_quit ();
	return;
}
//these callbacks used for 3 filter dialogs & others
/**
@brief close dialog @a dialog
@param widget the activated button, or NULL after a keypress
@param dialog the dialog widget
@return TRUE always
*/
gboolean e2_dialog_cancel_cb (GtkWidget *widget, GtkWidget *dialog)
{
  gtk_widget_destroy (dialog);
  gtk_widget_set_sensitive (app.main_window, TRUE);
//  gtk_widget_grab_focus (curr_view->treeview);
  return TRUE;
}
/**
@brief close dialog @a dialog when Esc key is pressed
@param widget the focused widget when the key was pressed
@param event pointer to event data struct
@param dialog the dialog widget
@return TRUE if Esc key was pressed
*/
gboolean e2_dialog_key_press_cb
	(GtkWidget *widget, GdkEventKey *event, GtkWidget *dialog)
{
  if (event->keyval == GDK_Escape)
    return e2_dialog_cancel_cb (NULL, dialog);
  return FALSE;
}
/**
@brief initiate the assigned 'negative response' for dialog @a data
@a data is unused here but is needed when [un]blocking this callback
@param widget the dialog widget which received the keypress
@param event pointer to event data struct
@param data pointer to dialog widget (= widget)
@return TRUE if Esc key was pressed
*/
gboolean e2_dialog_key_neg_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
//	printd (DEBUG, "e2_dialog_key_neg_cb (widget:_,event:_,data_)");
	if (event->keyval == GDK_Escape)
	{
		gint response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
			"e2-negative-response"));
		gtk_dialog_response (GTK_DIALOG (widget), response);
		return TRUE;
	}
	return FALSE;
}

/* BAD use what is currently focused, not always the positive response
gboolean e2_dialog_key_pos_cb (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	printd (DEBUG, "e2_dialog_key_pos_cb (widget:_,event:_,data_)");
	if (event->keyval == GDK_Return)
	{
		gint response = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (data),
				"e2-positive-response"));
		gtk_dialog_response (GTK_DIALOG (data), response);
		return TRUE;
	}
	return FALSE;
}
*/
/**
@brief setup various dialog window parameters
@param dialog the widget to be processed
@param parent the parent dialog widget of @a dialog
@return
*/
void e2_dialog_setup (GtkWidget *dialog, GtkWidget *parent)
{
	//some funcs here go down to xlib etc CHECKME problem if re-entrant ?
//	CLOSEBGL_IF_OPEN
	GtkWindow *thiswindow = GTK_WINDOW (dialog);
	if (parent != NULL)
		gtk_window_set_transient_for (thiswindow, GTK_WINDOW (parent));
	gtk_window_set_destroy_with_parent (thiswindow, TRUE);
	gtk_window_set_wmclass (thiswindow,
		gtk_window_get_title (thiswindow), PROGNAME);
	gtk_window_set_role (thiswindow, "dialog");
	gtk_window_set_position (thiswindow, e2_option_int_get ("dialog-position"));
	gtk_window_set_resizable (thiswindow, TRUE);  //moved to here from ... show
//	gtk_widget_set_size_request (dialog, 0, 0);	//KILLS LATESHOW  etc in this spot (at least) !!
//	OPENBGL_IF_CLOSED
}
/**
@brief setup and run dialog
@param dialog the widget to be processed
@param parent the parent dialog widget of @a dialog, or NULL
@param flags bitflags indicating how the dialog is to be run
@param button start of NULL-terminated sequence of button data-structs
@return the value returned from the dialog
*/
gint e2_dialog_show (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags,
	E2_Button *button, ...)
{
//	GtkWidget *lastbutton;  //just in case we want a pointer to the last 1
	//some of the calls here have no buttons
	//but we get warned if the flags button is made the last computsory one
	if (button != NULL)
	{
		va_list args;
		va_start (args, button);
		while (button != NULL)
		{
			//lastbutton =
			e2_dialog_add_defined_button (dialog, button);
			button = va_arg (args, E2_Button *);
		}
		va_end (args);
	}
	e2_dialog_setup (dialog, parent);
	return e2_dialog_run (dialog, parent, flags);
}
/**
@brief run dialog
This supports out-of-loop dialog single-setup
If modal, it's intended to be used for filelist-related dialogs, as the
active list is focused when the run is completed
@param dialog the widget to be processed
@param parent the parent dialog widget of @a dialog, or NULL
@param flags bitflags indicating how the dialog is to be run
@return the value returned from the dialog, 0 if the dialog is not run
*/
gint e2_dialog_run (GtkWidget *dialog, GtkWidget *parent, E2_DialogFlags flags)
{
//FIXME if single-setup used, need to re-select default button */
	if (flags & E2_DIALOG_THREADED)
		gdk_threads_enter ();
	if ((flags & E2_DIALOG_RUNMODAL) && (parent != NULL))
		gtk_widget_set_sensitive (parent, FALSE);
	if (flags & E2_DIALOG_DONT_SHOW_ALL)
		gtk_widget_show (dialog);	//this duplicates effect in gtk_dialog_run()
	else
		gtk_widget_show_all (dialog);
	if (flags & E2_DIALOG_THREADED)
		gdk_threads_leave ();

	if (flags & E2_DIALOG_RUNMODAL)
	{
		if (flags & E2_DIALOG_THREADED)
			gdk_threads_enter ();
		gint ret = gtk_dialog_run (GTK_DIALOG (dialog));
		if (parent != NULL)
			gtk_widget_set_sensitive (parent, TRUE);
		//ret could be GTK_RESPONSE_NONE or GTK_RESPONSE_DELETE_EVENT !!
		if (flags & E2_DIALOG_FREE)
			gtk_widget_destroy (dialog);
		gtk_widget_grab_focus (curr_view->treeview);
		if (flags & E2_DIALOG_THREADED)
			gdk_threads_leave ();
		return ret;
	}
	return 0;
}
/**
@brief create a dialog
If @a callback is NULL, the caller must locally handle "delete-event"
signals if anything special is needed for that
If @a label contains any text which looks like "bad" pango-markup, then the
label must have been escaped before calling here
@param stock name of gtk-stock-icon, or absolute localised path of icon file, to be shown near the top of the dialog, or NULL
@param label_text text to be shown near the top of the dialog, or NULL
@param title title string for the dialog, or NULL
@param callback response function for the dialog, or NULL
@param data pointer to data to be provided to @a callback
@return the dialog widget
*/
GtkWidget *e2_dialog_create (const gchar *stock, const gchar *label_text,
	const gchar *title, gpointer callback, gpointer data)
{
	GtkWidget *dialog = gtk_dialog_new ();

	//BGL may be open or closed here
	CLOSEBGL_IF_OPEN

	e2_window_set_title (dialog, title);

	gtk_widget_add_events (dialog, GDK_ALL_EVENTS_MASK);	//uses xlib?

	OPENBGL_IF_CLOSED

	GtkWidget *img = (stock != NULL ) ?
		e2_widget_get_icon (stock, GTK_ICON_SIZE_DIALOG) : NULL;

	if (img != NULL || label_text != NULL)
	{
		GtkWidget *hbox = gtk_hbox_new (FALSE, E2_PADDING);
		gtk_container_set_border_width (GTK_CONTAINER (hbox), E2_PADDING);
		g_object_set_data (G_OBJECT (dialog), "e2-dialog-hbox", hbox);

		if (img != NULL)
		{
//			gtk_misc_set_alignment (GTK_MISC (img), 0.5, 0.0);	//no effect
//			gtk_box_pack_start (GTK_BOX (hbox), img, FALSE, FALSE, E2_PADDING);
			gint isize = e2_utils_get_icon_size (GTK_ICON_SIZE_DIALOG);
			GtkWidget *align = gtk_alignment_new (1.0, 0.0, 0.0, 0.0);
			gtk_widget_set_usize (align, isize*1.2, isize);
			gtk_container_add (GTK_CONTAINER (align), img);
			gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0);
		}
		if (label_text != NULL)
		{
			GtkWidget *label = e2_widget_add_mid_label (hbox, label_text, 0.5, TRUE,
				(img != NULL) ? 2 : E2_PADDING);
			g_object_set_data (G_OBJECT (dialog), "e2-dialog-label", label);
		}

		gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, FALSE, FALSE, 0);
		gtk_widget_show_all (hbox);
	}

#ifdef E2_COMPOSIT
	e2_window_set_opacity (dialog, DIALOG_OPACITY_LEVEL);	//constant opacity for dialogs
#endif

//see above	gtk_widget_add_events (dialog, GDK_ALL_EVENTS_MASK);

	if (callback != NULL)
		g_signal_connect (G_OBJECT (dialog),
			"response", G_CALLBACK (callback), data);
	else
		g_signal_connect (G_OBJECT (dialog),
			"response", G_CALLBACK (_e2_dialog_response_cb), NULL);

	//the dialog may not have a CANCEL button, but the response will be decoded anyway
	e2_dialog_set_negative_response (dialog, GTK_RESPONSE_CANCEL);

	g_signal_connect (G_OBJECT (dialog), "key-press-event",
		G_CALLBACK (e2_dialog_key_neg_cb), dialog);
	//don't need a "delete-event" callback, that event generates a reponse GTK_RESPONSE_DELETE_EVENT

	return dialog;
}
/**
@brief create a vbox with scrolled window, in @a dialog
@param dialog the widget to hold the box
@return the vbox widget
*/
GtkWidget *e2_dialog_add_sw (GtkWidget *dialog)
{
	//start without scrollbars, so that the dialog is first displayed with the
	//size of the things packed into the sw
	GtkWidget *scrolled = e2_widget_add_sw (GTK_DIALOG (dialog)->vbox,
		GTK_POLICY_NEVER, GTK_POLICY_NEVER, TRUE, 0);
	//arrange for automatic scrollbars when the dialog is shown, after sizing
	g_signal_connect (G_OBJECT (GTK_DIALOG (dialog)), "show",
		G_CALLBACK (e2_dialog_show_cb), scrolled);
	GtkWidget *vbox = e2_widget_get_box (TRUE, FALSE, 0);
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled), vbox);
	gtk_viewport_set_shadow_type (GTK_VIEWPORT (GTK_BIN (scrolled)->child),
		GTK_SHADOW_NONE);
	GtkWidget *hbox = g_object_get_data (G_OBJECT (dialog), "e2-dialog-hbox");
	if (hbox != NULL)
	{
		gtk_widget_reparent (hbox, vbox);
		gtk_box_set_child_packing (GTK_BOX (vbox), hbox, FALSE, FALSE, 0,
			GTK_PACK_START);
	}
	gtk_widget_show (vbox);
	return vbox;
}
/**
@brief setup  to show/hide dialog button icon
This is a helper for the various add-button functions
@param stock string with name of icon that was requested
@return stock or NULL if no icon is to be shown
*/
static gchar *_e2_dialog_button_image_check (gchar *stock)
{
	return (e2_option_bool_get ("dialog-button-icons")) ?
		stock : NULL;
}
/**
@brief add a pre-defined button to the 'action-area' of @a dialog
Any custom tooltip will be cleared (and maybe freed) after button is added
@param dialog the widget to hold the button
@param button ptr to standard data struct for a defined button
@return the created button widget
*/
GtkWidget *e2_dialog_add_defined_button (GtkWidget *dialog, E2_Button *button)
{
	GtkWidget *retbutton = NULL;
	if (!(button->showflags & E2_BTN_HIDDEN))
	{
		gchar *tip = (button->showflags & E2_BTN_TIPPED) ? button->tip : NULL;
		retbutton = e2_button_get (button->label,
			_e2_dialog_button_image_check (button->stock), tip, NULL, NULL);
		gtk_dialog_add_action_widget (GTK_DIALOG (dialog), retbutton, button->response);
		if (button->showflags & E2_BTN_DEFAULT)
			gtk_dialog_set_default_response (GTK_DIALOG (dialog), button->response);
		else if (button->showflags & E2_BTN_GREY)
			gtk_widget_set_sensitive (retbutton, FALSE);
		gtk_widget_show_all (retbutton);
	}
	//revert to default button parameters, ready for next usage
	if (button->showflags & E2_BTN_RENAMED)
		button->label = gettext (button->default_label);
	if (button->showflags & E2_BTN_FREETIP)
	{
		g_free (button->tip);
		button->tip = NULL;
	}
	else if (button->showflags & E2_BTN_TIPPED)
		button->tip = NULL;
	button->showflags = button->default_flags;
	return retbutton;
}
/**
@brief add a simple custom button to the 'action-area' of @a dialog
@param dialog the widget to hold the button
@param stock string with icon name, or NULL
@param label string with button label
@param response the dialog response to be assigned to the button
@return the created button widget
*/
GtkWidget *e2_dialog_add_undefined_button
	(GtkWidget *dialog, gchar *stock, gchar *label, gint response)
{
	GtkWidget *button = e2_button_get (label,
		_e2_dialog_button_image_check (stock), NULL, NULL, NULL);
	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response);
	gtk_widget_show_all (button);
	return button;
}
/**
@brief add a custom button to the 'action-area' of @a dialog
@param dialog the widget to hold the button
@param is_default TRUE if the created button will be the default
@param button pointer to populated button data struct
@param tip	string with button tooltip
@param callback callback func when button is clicked
@param data ptr to data supplied to @a callback
@return the created button widget
*/
GtkWidget *e2_dialog_add_button_custom
	(GtkWidget *dialog, gboolean is_default, E2_Button *button,
		gchar *tip, void *callback, void *data)
{
	return e2_dialog_add_undefined_button_custom
		(dialog, is_default, button->response, button->label,
		_e2_dialog_button_image_check (button->stock), tip,
		callback, data);
}
/**
@brief add a custom 'unstructured' button to the 'action-area' of @a dialog
@param dialog the widget to hold the button
@param is_default TRUE if the created button will be the default
@param response the dialog response to be assigned to the button
@param label string with button label
@param stock string with icon name, or NULL
@param tip	string with button tooltip
@param callback callback func when button is clicked
@param data ptr to data supplied to @a callback
@return the created button widget
*/
GtkWidget *e2_dialog_add_undefined_button_custom
	( GtkWidget *dialog, gboolean is_default, guint response,
	gchar *label, gchar *stock, gchar *tip, gpointer callback, gpointer data )
{
	E2_ButtonFlags myflags = E2_BUTTON_CAN_FOCUS;
	if (is_default)
	{
		myflags |= E2_BUTTON_CAN_DEFAULT;
		gtk_dialog_set_default_response (GTK_DIALOG (dialog), response);
	}
	GtkWidget *button;
	button = e2_button_get_full (label,
		_e2_dialog_button_image_check (stock),
		GTK_ICON_SIZE_BUTTON, tip, callback, data, myflags);
	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response);
	gtk_widget_show_all (button);
	return button;
}
/**
@brief add a (gtk) toggle button to the 'action-area' of @a dialog
@param dialog the widget to hold the button
@param value T/F the initial setting of the toggle
@param label string with button label
@param tip string with button tooltip, or NULL
@param response the dialog response to be assigned to the button
@return the created button widget
*/
GtkWidget *e2_dialog_add_toggle_button
	(GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response)
{
	GtkWidget *button = e2_button_get_toggle (FALSE, value, label, tip, NULL, NULL);
	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response);
	return button;
}
/**
@brief add a check button to the 'action-area' of @a dialog
@param dialog the widget to hold the button
@param value T/F the initial setting of the toggle
@param label string with button label
@param tip string with button tooltip, or NULL
@param response the dialog response to be assigned to the button
@return the created button widget
*/
GtkWidget *e2_dialog_add_check_button
	(GtkWidget *dialog, gboolean value, gchar *label, gchar *tip, gint response)
{
	GtkWidget *button = e2_button_get_toggle (TRUE, value, label, tip, NULL, NULL);
	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response);
	return button;
}
/**
@brief "clicked" signal callback for file-chooser dialog toggle-hidden button

@param button the clicked button widget
@param chooser the chooser interface for the dialog

@return
*/
static void _e2_dialog_toggglehidden_cb (GtkButton *button, GtkFileChooser *chooser)
{
	//toggle hidden-items display & button-image
	gboolean nowhidden = gtk_file_chooser_get_show_hidden (chooser);
	gtk_file_chooser_set_show_hidden (chooser, ! nowhidden);
	e2_button_set_image (GTK_WIDGET (button), (nowhidden) ?
		"hidden_show_"E2IP".png" : "hidden_noshow_"E2IP".png");
}
/**
@brief setup a file-chooser dialog
The actual dialog is not created here as there's no passthrough of variadic
args for button-icons and -signals.

@param dialog the newly-created file-chooser-dialog
@param title title string for the dialog, or NULL
@param startpath initial item to select in the dialog (absolute utf8 path), or NULL
@param action enumerator of type of chooser dialog
@param showhidden TRUE to start with hidden items displayed
@param multi TRUE if selection of multiple items is allowed
@param defresponse the default response for the dialog

@return the filechooser widget thai's part of the dialog
*/
void e2_dialog_setup_chooser (GtkWidget *dialog, const gchar *title,
	const gchar *startpath, GtkFileChooserAction action, gboolean showhidden,
	gboolean multi, gint defresponse)
{
	//FIXME vfs
	gchar *local, *s;
	GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);

/*	GtkWidget *dialog = e2_dialog_create (NULL, NULL, title,
		_e2_some_response_cb, somedata);
	GtkWidget *vbox = GTK_DIALOG (dialog)->vbox;
	gtk_box_pack_start (GTK_BOX(vbox), wchooser, TRUE, TRUE, 0);
	e2_dialog_setup (dialog, parent);
*/
	e2_window_set_title (dialog, title);
#ifdef E2_COMPOSIT
	e2_window_set_opacity (dialog, DIALOG_OPACITY_LEVEL);	//constant opacity for dialogs
#endif

/*	//hide the dialog's standard 'open' button
	GtkContainer *bbox = GTK_CONTAINER (GTK_DIALOG (dialog)->action_area);
	GList* children = gtk_container_get_children (bbox);
	gtk_widget_hide ((GtkWidget *)children->data);
	g_list_free (children);
*/
	GtkWidget *hidebtn = e2_dialog_add_undefined_button_custom
		(dialog, FALSE, E2_RESPONSE_USER1, _("_Hidden"),
		(showhidden) ? "hidden_noshow_"E2IP".png" : "hidden_show_"E2IP".png",
		_("Toggle display of hidden items"),
		_e2_dialog_toggglehidden_cb, chooser);
	GtkWidget *box = (GTK_DIALOG (dialog))->action_area;
	gtk_box_reorder_child (GTK_BOX (box), hidebtn, 0);

#ifdef USE_GTK2_8
	if ((action == GTK_FILE_CHOOSER_ACTION_SAVE
		|| action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER
		|| action == GTK_FILE_CHOOSER_ACTION_OPEN)
		&& e2_option_bool_get ("confirm-overwrite"))
		gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
#endif
	gtk_dialog_set_default_response (GTK_DIALOG (dialog), defresponse);
	gtk_file_chooser_set_select_multiple (chooser, multi);
	if (showhidden)
		gtk_file_chooser_set_show_hidden (chooser, TRUE);

	if (startpath != NULL)
	{
		local = D_FILENAME_TO_LOCALE (startpath);
		s = local + strlen (local) - 1;
		if (*s == G_DIR_SEPARATOR)
		{	//no file named, need to trick selector into opening correct dir
			s = local;
			local = e2_utils_strcat (local, " ");
			g_free (s);
		}
		gtk_file_chooser_set_filename (chooser, local);
		g_free (local);
	}
	else
	{
#ifdef E2_VFSTMP
		FIXME
#else
		local = D_FILENAME_TO_LOCALE (curr_view->dir);
		s = local + strlen (local) - 1;
		if (*s == G_DIR_SEPARATOR)
			*s = '\0';
	    gtk_file_chooser_set_current_folder (chooser, local);
		g_free (local);
#endif
//    	gtk_file_chooser_set_current_name (chooser,
//			"Untitled document");	//this must be utf8
	}

//FIXME change size: here or after mapping or showing does not work
//	gint window_width = MIN(400, app.window.panes_paned->allocation.width*2/3);
//		gtk_window_resize (GTK_WINDOW (dialog), window_width, -1);
//	gint width, height;
//	gtk_window_get_size (GTK_WINDOW (dialog), &width, &height);
//	width = MIN(width, app.window.panes_paned->allocation.width*2/3);
//	gtk_window_resize (GTK_WINDOW (dialog), width*2, height*6);
//	gtk_window_resize (GTK_WINDOW (dialog), width, -1);
//	g_signal_connect (G_OBJECT (dialog), "map", //"show",
//		G_CALLBACK (_e2_dialog_show_cb), NULL);

	//the dialog may not have a CANCEL button, but the response will be decoded anyway
	e2_dialog_set_negative_response (dialog, GTK_RESPONSE_CANCEL);

	g_signal_connect (G_OBJECT (dialog), "key-press-event",
		G_CALLBACK (e2_dialog_key_neg_cb), dialog);
	//don't need a "delete-event" callback, that event generates a reponse GTK_RESPONSE_DELETE_EVENT

//	e2_dialog_setup (dialog, parent);
}
/**
@brief display dialog to get user input with obfuscated text display, for passwords
@param window_title window title string
@param prompt prompt string
@param input pointer to store for the inputted password string
@return button code returned by the dialog
*/
/* tag PASSWORDINPUT
DialogButtons e2_dialog_password_input (gchar* window_title,
	gchar *prompt, gchar **input)
{
	gchar *realprompt = (prompt != NULL) ? prompt : _("Enter password:");
	return _e2_dialog_line_input (window_title, realprompt,
		NULL, FALSE, TRUE, input, 0, FALSE, NULL);
} */
/**
@brief display dialog to get user input
@param window_title window title string
@param prompt prompt string
@param suggestion suggested answer string
@param extras button-related flags
@param select_text TRUE to select the displayed @a suggestion
@param input pointer to store for the inputted password string
@return button code returned by the dialog
*/
DialogButtons e2_dialog_line_input (gchar* window_title, gchar *prompt,
	gchar *suggestion, OW_ButtonFlags extras, gboolean select_text,
	gchar **input)
{
	return _e2_dialog_line_input (window_title, prompt,
		suggestion, select_text,// tag PASSWORDINPUT FALSE,
		input, extras, FALSE, NULL);
}
/**
@brief display dialog to get user input, via a combobox
@param window_title window title string
@param prompt prompt string
@param suggestion string to show in combobox entry, or NULL
@param extras button-related flags
@param history_list the history list for a combo entry
@param input pointer to store for the entered string
@return button code returned by the dialog
*/
DialogButtons e2_dialog_combo_input (gchar* window_title,
	gchar *prompt, gchar *suggestion, OW_ButtonFlags extras,
	GList **history_list, gchar **input)
{
	return _e2_dialog_line_input (window_title, prompt, suggestion,
		TRUE, // tag PASSWORDINPUT FALSE,
		input, extras, TRUE, history_list);
}
/**
@brief generic dialog for user line-input
@param window_title window title string
@param prompt prompt string
@param suggestion suggested answer string
@param extras button-related flags
@param select_text TRUE to select the displayed @a suggestion
@param input pointer to store for the entered string
@param history TRUE to make the input a combobox with history
@param history_list the history list for a combo entry
@return button code returned by the dialog
*/
static DialogButtons _e2_dialog_line_input (gchar* window_title, gchar *prompt,
	gchar *suggestion, gboolean select_text, // tag PASSWORDINPUT gboolean hide_text,
	gchar **input, OW_ButtonFlags extras, gboolean history, GList **history_list)
{
	GtkWidget *dialogentry;
	GtkWidget *dialogcombo = NULL; 	//assignment for compiler-warning prevention only

/*	GtkWidget *dialog = get_dialog (app.main_window, GTK_MESSAGE_QUESTION,
#ifdef LATESHOW
	"");
#else
	prompt);
#endif
	gchar *title = (window_title != NULL) ? window_title : _("user input");
	e2_dialog_set_title (dialog, title);
*/
	gchar *title = (window_title != NULL) ? window_title : _("user input");
	DialogButtons retval;
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION,
#ifdef LATESHOW
		"",
#else
		prompt,
#endif
		title, e2_dialog_response_decode_cb, &retval);

	if (history)
	{
		dialogcombo = e2_combobox_add (GTK_DIALOG (dialog)->vbox,
			FALSE, E2_PADDING, NULL, NULL, history_list,
			E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_FOCUS_ON_CHANGE);
		dialogentry = GTK_BIN (dialogcombo)->child;
		if (suggestion != NULL)
		{
			gtk_entry_set_text (GTK_ENTRY (dialogentry), suggestion);
			//select_text is TRUE
			gtk_editable_select_region (GTK_EDITABLE (dialogentry), 0, -1);
		}
	}
	else
		dialogentry = e2_widget_add_entry (GTK_DIALOG (dialog)->vbox,
#ifdef LATESHOW
			NULL, FALSE, FALSE);
#else
			suggestion, FALSE, select_text);
#endif

#ifdef E2_ASSISTED
	GtkWidget *label = (GtkWidget *) g_object_get_data (G_OBJECT (dialog),
		"e2-dialog-label");
	e2_widget_set_label_relations (GTK_LABEL (label), dialogentry);
#endif

	//handle Return-key presses when the entry is focused
	g_signal_connect (G_OBJECT (dialogentry), "activate",
		G_CALLBACK (_e2_dialog_activated_cb), &retval);

/* tag PASSWORDINPUT
	if (hide_text)
		gtk_entry_set_visibility (GTK_ENTRY (dialogentry), FALSE); */

// these separated, to do them once for re-run dialogs
	if (extras)
	{
		if (extras & NOALL)
		{
			E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY;
			e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL);
		}
		else if (!(extras & BOTHALL))
		{
			E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY;
			E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY;
		}
		e2_dialog_add_defined_button (dialog, &E2_BUTTON_NOTOALL);
		e2_dialog_add_defined_button (dialog, &E2_BUTTON_YESTOALL);
	}
	//set default button to 'cancel'
//	E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
//	E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;

	e2_dialog_add_defined_button (dialog, &E2_BUTTON_CANCEL);
	e2_dialog_add_defined_button (dialog, &E2_BUTTON_OK);

	e2_dialog_setup (dialog, app.main_window);

#ifdef LATESHOW
// FIXME figure out how to do all the above once-only (if needed) instead of each dialog re-run
// -------------------------------------------------------------------------------------------
// add prompt to dialog now (eg in re-run)instead of at the start (1st-time)
//	GtkLabel *label = GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label);
	GtkLabel *label = GTK_LABEL (GTK_DIALOG (dialog)->label);
	gtk_label_set_text (label, prompt);
	gtk_label_set_use_markup (label, TRUE);
#ifdef E2_ASSISTED
	e2_widget_set_label_relations (label, dialogentry);
#endif

//  & add default/suggestion now
	if (history)
	{
		//CHECKME set to last-used entry?
//		if (history_list != NULL)
//			e2_combobox_set_active (dialogcombo, 0)
	}
	else
	{
		gtk_entry_set_text (GTK_ENTRY (dialogentry), suggestion);
		if (select_text)
			gtk_editable_select_region (GTK_EDITABLE (dialogentry), 0, -1);
	}
#endif

	//CHECKME uses of this other than in renaming tasks are ok with this non-sensitive ?
	e2_dialog_run (dialog, app.main_window, 0);  //don't desensitize the parent window
	gtk_main ();
	if (retval == OK)
	{
		*input = gtk_editable_get_chars (GTK_EDITABLE (dialogentry), 0, -1 ); //free this after return
		if (**input == '\0')
		{	//dopey user supplied a blank name
			g_free (*input);
			retval = CANCEL;
		}
		else if (history)
			e2_list_update_history (*input, history_list, NULL, 10, FALSE);
	}
// FIXME just hide the window, if so specified
// in the meantime ...
	gtk_widget_destroy (dialog);
	return retval;
}
/**
@brief dialog for overwrite check
Assumes that @a dlocal (at least) exists
@param slocal localised string with source-item path, or NULL for no date-check
@param dlocal localised string with destination-item path
@param extras flags for which buttons to show
@return button code returned by the dialog
*/
DialogButtons e2_dialog_ow_check (gchar *slocal, gchar *dlocal, OW_ButtonFlags extras)
{
//	printd (DEBUG, "overwrite check");
#ifdef E2_VFS
	VPATH ddata;
#endif
	struct stat sb;

	GString *dialog_prompt = g_string_sized_new (NAME_MAX + 64);	//pick a reasonable initial size
	gchar *utf = F_DISPLAYNAME_FROM_LOCALE (dlocal);
	//before applying bold markup, escape any pango-annoying component of the item name
	gchar *public = g_markup_escape_text (utf, -1);
	gchar *type = NULL;
	if (slocal != NULL)
	{
//		E2_ERR_DECLARE

#ifdef E2_VFS
		ddata.localpath = slocal;
#ifdef E2_VFSTMP
		//FIXME spacedata
#endif
		ddata.spacedata = NULL;
		if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE()))
#else
		if (!e2_fs_stat (slocal, &sb E2_ERR_NONE()))	//should never fail
#endif
		{
			gboolean sdir = S_ISDIR (sb.st_mode);
			time_t stime = sb.st_mtime;
#ifdef E2_VFS
			ddata.localpath = dlocal;
#ifdef E2_VFSTMP
		//FIXME spacedata
#endif
			if (e2_fs_stat (&ddata, &sb E2_ERR_NONE()))
#else
			if (e2_fs_stat (dlocal, &sb E2_ERR_NONE()))
#endif
			{
				//FIXME handle error properly - maybe nothing to overwrite now
				//E2_ERR_CLEAR
			}
			else if (sdir || !S_ISDIR (sb.st_mode))	//no special treatment for file-to-dir
			{
				if (stime > sb.st_mtime)
					type = _("older");	//dest is older
				else if (stime < sb.st_mtime)
					type = _("newer");	//source is older
			}
		}
//#ifdef E2_VFS
//		else
//			E2_ERR_CLEAR
//#endif
	}
	if (type == NULL)
		type = _("existing");

#ifdef E2_VFS
	ddata.localpath = dlocal;
# ifdef E2_VFSTMP
		//FIXME spacedata
# endif
	ddata.spacedata = NULL;
	if (!e2_fs_lstat (&ddata, &sb E2_ERR_NONE())
#else
	if (!e2_fs_lstat (dlocal, &sb E2_ERR_NONE())	//should never fail
#endif
		&& S_ISDIR (sb.st_mode)
		&& !S_ISLNK (sb.st_mode))
			g_string_printf (dialog_prompt,
				_("Remove all contents of %s\n<b>%s</b> ?"), type, public);
	else
	{
		gchar *dbase = g_path_get_basename (public);
		gchar *dpath = g_path_get_dirname (public);
		g_string_printf (dialog_prompt, _("Overwrite %s <b>%s</b>\nin %s ?"),
			type, dbase, dpath);
		g_free (dbase);
		g_free (dpath);
	}
	DialogButtons retval;
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING,
		dialog_prompt->str, _("confirm"), e2_dialog_response_decode_cb, &retval);

	F_FREE (utf);
	g_free (public);
	g_string_free (dialog_prompt, TRUE);
//FIXME adjust this to suit single-setup when multi is TRUE
	if (extras & NOALL)
	{
		E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY;
		e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL);
	}
	else if (!(extras & BOTHALL))
	{
		E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY;
		E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY;
	}
	//set default button to 'no'
	E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT;
	E2_BUTTON_YES.showflags &= ~E2_BTN_DEFAULT;
	//non-modal dialog
	e2_dialog_show (dialog, app.main_window,
		0,  //no  _RUN (hence window stays sensitive)  no _FREE, in case we want to hide it
			&E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL,
			&E2_BUTTON_NO, &E2_BUTTON_YES, NULL);
	gtk_main ();
	// FIXME just hide the window, if so specified
	// in the meantime ...
	gtk_widget_destroy (dialog);

	return retval;
}
/**
@brief run warning dialog for confirmation
@param prompt prompt string
@return button code returned by the dialog
*/
DialogButtons e2_dialog_warning (gchar *prompt)
{
	DialogButtons retval;
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING,
		prompt, _("confirm"), e2_dialog_response_decode_cb, &retval);
	//set default button to 'cancel'
	E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
	E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;
	e2_dialog_show (dialog, NULL,	//parent stays sensitive
		0, &E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL);
	gtk_main ();	//because the decoder has gtk_main_quit();
	gtk_widget_destroy (dialog);
	return retval;
}
/**
@brief create "too slow" dialog
There is no actual display of anything, so gdk mutex management is not done
(assumes BGL is on)
Note: when juggling dialog between threads, avoid gtk_main[_quit] usage in
dialog display code and response cb.
@param prompt_type context-specific part of prompt string
@param tip_type context-specific part of cancel-button tip
@param response_func pointer to function to use for dialog's "response" callback, or NULL
@param data data to provide to the dialog's response callback

@return the dialog widget
*/
GtkWidget *e2_dialog_slow (gchar *prompt_type, gchar *tip_type,
	gpointer response_func, gpointer data)
{
	gchar *prompt = g_strdup_printf (
		_("%s is taking a long time. Continue waiting ?"), prompt_type);
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION, prompt,
		_("confirm"),
		(response_func != NULL) ? response_func : _e2_dialog_response_cb, data);
	g_free (prompt);

	E2_BUTTON_YESTOALL.showflags |= (E2_BTN_RENAMED | E2_BTN_TIPPED);
	E2_BUTTON_YESTOALL.label = _("_Quiet");
	E2_BUTTON_YESTOALL.tip = _("Don't ask any more");
	e2_dialog_add_defined_button (dialog, &E2_BUTTON_YESTOALL);

	E2_BUTTON_NO.showflags |= (E2_BTN_TIPPED | E2_BTN_FREETIP);
	E2_BUTTON_NO.tip = g_strdup_printf (_("Cancel the %s"), tip_type);
	e2_dialog_add_defined_button (dialog, &E2_BUTTON_NO);

	E2_BUTTON_YES.showflags |= E2_BTN_TIPPED;
	E2_BUTTON_YES.tip = _("Wait some more");
	e2_dialog_add_defined_button (dialog, &E2_BUTTON_YES);

	e2_dialog_set_negative_response (dialog, GTK_RESPONSE_YES);
	e2_dialog_setup (dialog, app.main_window);
	return dialog;
}
/**
@brief setup context menu for @a dialog
@param dialog the dialog to which @a menu belongs
@param menu context menu for @a dialog
@return
*/
void e2_dialog_attach_menu (GtkWidget *dialog, GtkWidget *menu)
{
	g_return_if_fail ((dialog != NULL) && (menu != NULL));
	g_signal_connect (G_OBJECT (dialog), "button-press-event",
		G_CALLBACK (_e2_dialog_button_press_cb), menu);
	g_object_set_data_full (G_OBJECT (dialog), "e2-context-menu",  menu,
		(GDestroyNotify) gtk_widget_destroy);
}

/* UNUSED
void e2_dialog_set_positive_response (GtkWidget *dialog, gint response)
{
	g_object_set_data (G_OBJECT (dialog), "e2-positive-response",
		GINT_TO_POINTER (response));
//	gtk_dialog_set_default_response (GTK_DIALOG (dialog), response);
} */
/**
@brief assign the 'negative' response for @a dialog
@param dialog the dialog widget to be processed
@param response the number of the "negative" response
@return
*/
void e2_dialog_set_negative_response (GtkWidget *dialog, gint response)
{
	g_object_set_data (G_OBJECT (dialog), "e2-negative-response",
		GINT_TO_POINTER (response));
}

/* UNUSED as we only use the -ve response, now
void e2_dialog_set_responses (GtkWidget *dialog, gint pos, gint neg)
{
	e2_dialog_set_positive_response (dialog, pos);
	e2_dialog_set_negative_response (dialog, neg);
} */
/**
@brief proportionally change size of @a dialog
@param dialog the dialog widget to be resized
@param factor multiple to apply to width and height of @a dialog
@return
*/
void e2_dialog_resize (GtkWidget *dialog, gfloat factor)
{
	gint width, height;
	gtk_window_get_size (GTK_WINDOW (dialog), &width, &height);
	width *= factor;
	height *= factor;
	gtk_window_resize (GTK_WINDOW (dialog), width, height);
}
/**
@brief setup to resize @a dialog at an idle time
@param dialog the dialog widget to be resized
@param scrolled a scrolled window widget in @a dialog, or NULL
@return
*/
void e2_dialog_resize_with_sw (GtkWidget *dialog, GtkWidget *scrolled)
{
	guint idle_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (dialog),
			"e2-dialog-resize-idle-id"));
	if (idle_id != 0)
		g_source_remove (idle_id);
	idle_id = g_idle_add ((GSourceFunc) _e2_dialog_resize_idle_cb, dialog);
	g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-idle-id",
		GUINT_TO_POINTER (idle_id));
	g_object_set_data (G_OBJECT (dialog), "e2-dialog-resize-scrolled",
		scrolled);
	g_signal_connect (G_OBJECT (dialog), "destroy",
		G_CALLBACK (_e2_dialog_resize_destroy_cb), NULL);
}
/**
@brief set dialog window cursor to @a type
Expects BGL on/active
@param dialog the affected dialog widget
@param type enumerator of desired cursor type

@return
*/
void e2_dialog_set_cursor (GtkWidget *dialog, GdkCursorType type)
{
	GdkCursor *cursor = gdk_cursor_new (type);
	gdk_window_set_cursor (dialog->window, cursor);
	gdk_cursor_unref (cursor);
	gdk_flush ();
}
/**
@brief register dialog-related options
@return
*/
void e2_dialog_options_register (void)
{
	const gchar *opt_dialog_pos_position[] = {_("none"), _("center"), _("mouse"),
		_("always center"), _("center on parent"), NULL};
	gchar *group_name = g_strconcat(_C(20),":",_C(25),NULL);  //_("interface:miscellaneous"
	e2_option_sel_register ("dialog-position", group_name, _("dialog position"),
		_("This determines the position where dialog windows will pop up"), NULL, 1,
		opt_dialog_pos_position,
		E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
}
