/* $Id: e2_filetype_dialog.c 854 2008-04-19 11:45:33Z 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
*/

#include "emelfm2.h"
#include <string.h>
#include "e2_dialog.h"
#include "e2_task.h"
#include "e2_filetype.h"

#ifndef OLDFTDLG
//no translation of this
#define E2_FTDLG_SIG "PANE%d-FTDLG"

  /*****************************/
 /*** edit filetypes dialog ***/
/*****************************/

/* this dialog uses the config treestore for the filetypes option,
and also 2 special-purpose treestores and 3 treeviews, respectively
for categories, extensions and commands. The latter 2 have no
children, but cannot be liststores coz the cut/copy/paste functions
work only on treestores.
Contents of the latter 2 are moved back and forth from the main config
treestore as needed, notably when a different category is selected */

//FIXME doesn't like config dialog filetypes being edited when this is open
//FIXME saving to invalid path in config treestore, create new cat & use its path

typedef enum
{
	E2_CATPANE,
	E2_EXTPANE,
	E2_CMDPANE,
	E2_PANECOUNT
} E2_FileTypeDlg_PaneID;

typedef struct _E2_FileTypeDlgRuntime
{
	E2_OptionSet *typeset;
	E2_FileTypeDlg_PaneID currentID;
	void *stores[E2_PANECOUNT];	//GtkTreeStores
	GtkWidget *views[E2_PANECOUNT];
	GtkTreePath *catpath;
	GHashTable *buffer_hash;	//for cut|copy data
} E2_FileTypeDlgRuntime;

static gboolean _e2_edftdlg_cancel_cb (GtkWidget *widget, E2_FileTypeDlgRuntime *rt);
static GtkWidget *filetypes_dialog = NULL;

/**
@brief save current-category data back into the config option treestore
Current category is already logged at rt->catpath
@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_save_current_category (E2_FileTypeDlgRuntime *rt)
{
	GtkTreeModel *cfgmdl  = rt->typeset->ex.tree.model;
	GtkTreeIter cfg_iter;
	GtkTreePath *path = rt->catpath;
	if (path == NULL //there was no category
		|| !gtk_tree_model_get_iter (cfgmdl, &cfg_iter, path))
		return;

	GtkTreeModel *mdl;
	gchar *extension, *cmd_label, *cmd_string;
	GtkTreeIter iter, cfg_iter2, cfg_iter3;	//iters for local liststore & 3 levels of filetypes treestore
	gint cfgi, newi, cfgchildren, newchildren;

	//there should never be a case where the extensions and commands child-nodes
	//are missing, but ...
	if (gtk_tree_model_iter_children (cfgmdl, &cfg_iter2, &cfg_iter))
	{
		do
		{  // extension or command loop = level 2
			cfgchildren = gtk_tree_model_iter_n_children (cfgmdl, &cfg_iter2);
			gtk_tree_model_get (cfgmdl, &cfg_iter2, 1, &extension, -1);
			if (g_str_equal (extension, _C(13)))  //extensions node found
			{
				g_free (extension);
				mdl = GTK_TREE_MODEL (rt->stores[E2_EXTPANE]);
				newchildren = gtk_tree_model_iter_n_children (mdl, NULL);
				//replace as many of old values as possible
				cfgi = 0;
				for (newi = 0; newi < newchildren; newi++)
				{
					gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi);
					gtk_tree_model_get (mdl, &iter, 0, &extension, -1);
					if (extension != NULL)
					{
						if (*extension != '\0')
						{
							if (cfgi == cfgchildren)
							{
								g_free (extension);
								break;
							}
							gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, cfgi);
							gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3, 1, extension, -1);
							cfgi++;
						}
						g_free (extension);
					}
				}
				//if we ran out of old children, need to add more
				for (;newi < newchildren; newi++)
				{
					gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi);
					gtk_tree_model_get (mdl, &iter, 0, &extension, -1);
					if (extension != NULL)
					{
						if (*extension != '\0')
						{
#ifdef USE_GTK2_10
							gtk_tree_store_insert_with_values (
								rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1,
#else
							//insert empty row into the config treestore, after other children
							gtk_tree_store_insert (rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1);
							//populate it
							gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3,
#endif
								1, extension, -1);

						}
						g_free (extension);
					}
				}
			}
			else if (g_str_equal (extension, _C(6)))  //commands node found
			{
				g_free (extension);
				mdl = GTK_TREE_MODEL (rt->stores[E2_CMDPANE]);
				newchildren = gtk_tree_model_iter_n_children (mdl, NULL);
				//replace as many of old values as possible
				cfgi = 0;
				for (newi = 0; newi < newchildren; newi++)
				{
					gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi);
					gtk_tree_model_get (mdl, &iter, 0, &cmd_label, 1, &cmd_string, -1);
					if (cmd_string != NULL)
					{
						if (*cmd_string != '\0')
						{
							if (cfgi == cfgchildren)
							{
								if (cmd_label != NULL)
									g_free (cmd_label);
								g_free (cmd_string);
								break;
							}
							if (cmd_label == NULL)
								cmd_label = g_strdup ("?");	//tolerate blank labels
							else if (*cmd_label == '\0')
							{
								g_free (cmd_label);
								cmd_label = g_strdup ("?");
							}
							gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, cfgi);
							gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3,
								1, cmd_label, 2, cmd_string, -1);
							cfgi++;
						}
						g_free (cmd_string);
					}
					g_free (cmd_label);
				}
				//if we ran out of old children, need to add more
				for (;newi < newchildren; newi++)
				{
					gtk_tree_model_iter_nth_child (mdl, &iter, NULL, newi);
					gtk_tree_model_get (mdl, &iter, 0, &cmd_label, 1, &cmd_string, -1);
					if (cmd_string != NULL && *cmd_string != '\0')
					{
						if (cmd_label == NULL)
							cmd_label = g_strdup ("?");	//tolerate blank labels
						else if (*cmd_label == '\0')
						{
							g_free (cmd_label);
							cmd_label = g_strdup ("?");
						}
#ifdef USE_GTK2_10
						gtk_tree_store_insert_with_values (
							rt->stores[E2_CATPANE], &cfg_iter3, &cfg_iter2, -1,
#else
						//insert empty row into the config treestore, after other children
						gtk_tree_store_insert (rt->stores[E2_CATPANE], &cfg_iter3,
							&cfg_iter2, -1);
						//populate it
						gtk_tree_store_set (rt->stores[E2_CATPANE], &cfg_iter3,
#endif
							1, cmd_label, 2, cmd_string, -1);
					}
					if (cmd_label != NULL)
						g_free (cmd_label);
					if (cmd_string != NULL)
						g_free (cmd_string);
				}
			}
			else
				continue;	//OOPS - what else is there ?
			//delete any redundant old children
			//(by repeatedly deleting the next child)
			newi = cfgi;	//re-use this variable
			for (; cfgi < cfgchildren; cfgi++)
			{
				gtk_tree_model_iter_nth_child (cfgmdl, &cfg_iter3, &cfg_iter2, newi);
				gtk_tree_store_remove (rt->stores[E2_CATPANE], &cfg_iter3);
			}
		} while (gtk_tree_model_iter_next (cfgmdl, &cfg_iter2));
	}
}
/**
@brief (re)populate extensions- and commands-view for the category at @a path in the categories-view

@a path is logged, so we can always find the
currently-displayed category item
Extensions and commands stores are cleared, so
anything there that we want to keep must already be saved
@param path ptr to treeview path of the category to be used
@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_update_category (GtkTreePath *path,
	E2_FileTypeDlgRuntime *rt)
{
	gtk_tree_store_clear (rt->stores[E2_EXTPANE]);
	gtk_tree_store_clear (rt->stores[E2_CMDPANE]);

	GtkTreeIter iter;
	GtkTreeModel *cfgmdl = rt->typeset->ex.tree.model;
	if (gtk_tree_model_get_iter (cfgmdl, &iter, path))
	{
		GtkTreeIter newiter;	//for dialog stores
		GtkTreeIter iter2, iter3;	//for cfg levels
		gchar *extension, *cmd_label, *cmd_string;
		//should always be level-2 child nodes, but test anyway ...
		if (gtk_tree_model_iter_children (cfgmdl, &iter2, &iter))
		{
			do
			{  // extension or command loop = level 2
				gtk_tree_model_get (cfgmdl, &iter2, 1, &extension, -1);
				if (gtk_tree_model_iter_children (cfgmdl, &iter3, &iter2))
				{
					if (g_str_equal (extension, _C(13)))  //extensions node found
					{
						g_free (extension);
						do
						{  //extension loop = level 3, fill the extensions store
							gtk_tree_model_get (cfgmdl, &iter3, 1, &extension, -1);
#ifdef USE_GTK2_10
							gtk_tree_store_insert_with_values (
								rt->stores[E2_EXTPANE], &newiter, NULL, -1,
#else
							//append empty row to the store
							gtk_tree_store_append (rt->stores[E2_EXTPANE], &newiter, NULL);
							//populate it
							gtk_tree_store_set (rt->stores[E2_EXTPANE], &newiter,
#endif
								0, extension, -1);
							g_free (extension);
						} while (gtk_tree_model_iter_next (cfgmdl, &iter3));
					}
					else if (g_str_equal (extension, _C(6)))  //commands node found
					{
						g_free (extension);
						do
						{  //commands loop also = level 3, build the commands array
							gtk_tree_model_get (cfgmdl, &iter3, 1, &cmd_label, 2,
								&cmd_string, -1);
#ifdef USE_GTK2_10
							gtk_tree_store_insert_with_values (
								rt->stores[E2_CMDPANE], &newiter, NULL, -1,
#else
							//append empty row to the store
							gtk_tree_store_append (rt->stores[E2_CMDPANE], &newiter, NULL);
							//populate it
							gtk_tree_store_set (rt->stores[E2_CMDPANE], &newiter,
#endif
								0, cmd_label, 1, cmd_string, -1);
							g_free (cmd_label);
							g_free (cmd_string);
						} while (gtk_tree_model_iter_next (cfgmdl, &iter3));
					}
					else
					{ //OOPS
						printd (WARN, "un-recognised node in filetypes config");
					}
				}
				else
					g_free (extension); //no children, just cleanup
			} while (gtk_tree_model_iter_next (cfgmdl, &iter2));

		}
	}

	//remember where to find the category now displayed
	if (rt->catpath != NULL)
		gtk_tree_path_free (rt->catpath);
	rt->catpath = gtk_tree_path_copy (path);
}
/**
@brief treeview cell edited callback

@param renderer the renderer that was used
@param path_string string form of path of edited cell
@param new_text the replacement content for the edited cell
@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_cell_edited_cb (GtkCellRendererText *renderer,
	gchar *path_string, gchar *new_text, E2_FileTypeDlgRuntime *rt)
{
	if (new_text == NULL) return;	//the user did not finish editing a new cell
	GtkTreeIter iter;
	gtk_tree_model_get_iter_from_string
		(GTK_TREE_MODEL (rt->stores[rt->currentID]), &iter, path_string);
	gpointer colptr = g_object_get_data (G_OBJECT (renderer), "col_num");
	gint colnum = GPOINTER_TO_INT (colptr);
	if (colnum == -1)	//this is the categories renderer
		colnum = 0;
	gtk_tree_store_set (rt->stores[rt->currentID], &iter, colnum, new_text, -1);
}
/**
@brief category treeview selection-changed callback

@param treeselection selection object for the clicked treeview widget
@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_cat_sel_change_cb (GtkTreeSelection *treeselection,
	E2_FileTypeDlgRuntime *rt)
{
	GtkTreePath *path;
	gtk_tree_view_get_cursor (GTK_TREE_VIEW (rt->views[E2_CATPANE]), &path, NULL);
	if (path != NULL)
	{
		_e2_edftdlg_save_current_category (rt);
		_e2_edftdlg_update_category (path, rt);
		gtk_tree_path_free (path);
	}
}
/**
@brief determine which pane's store is current, and the first selected iter

@param store pointer to store for current-store pointer
@param iter pointer to iter to set to the first selected item in store

@return TRUE if iter is set
*/
static gboolean _e2_edftdlg_find_selected
	(E2_FileTypeDlgRuntime *rt, GtkTreeIter *iter)
{
	GtkTreeSelection *sel =
		gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[rt->currentID]));
	GtkTreeModel *mdl;
	if (rt->currentID == E2_CATPANE)
		//categories treeview is single-selection only
		return (gtk_tree_selection_get_selected (sel, &mdl, iter));

	gboolean retval;
	GList *items = gtk_tree_selection_get_selected_rows (sel, &mdl);
	if (items != NULL)
	{
		retval = gtk_tree_model_get_iter (mdl, iter, (GtkTreePath *)items->data);
		g_list_foreach (items, (GFunc) gtk_tree_path_free, NULL);
		g_list_free (items);
	}
	else
		retval = FALSE;
	return retval;
}
/**
@brief process dialog pane change
This is a "focus-in-event" callback, applied to each treeview

@param treeview the newly-focused treeview widget
@param event ptr to event data struct
@param rt pointer to data struct for the dialog

@return FALSE always - allow other handlers
*/
static gboolean _e2_edftdlg_focus_in_cb (GtkWidget *treeview,
	GdkEventFocus *event, E2_FileTypeDlgRuntime *rt)
{
	if (treeview == rt->views[E2_CATPANE])
		rt->currentID = E2_CATPANE;
	else if (treeview == rt->views[E2_EXTPANE])
		rt->currentID = E2_EXTPANE;
	else
		rt->currentID = E2_CMDPANE;
	GtkTreeIter iter;
	if (!_e2_edftdlg_find_selected (rt, &iter))
	{
		GtkTreePath*path = gtk_tree_path_new_first ();
		//NOTE this is supposed to, but doesn't, make the cell editable
		//without a mouse-click !
		gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview), path, NULL, TRUE);
		gtk_tree_path_free (path);
	}
	return FALSE;
}
/**
@brief insert a row into store and view

@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_insert_row (E2_FileTypeDlgRuntime *rt)
{
	GtkTreeIter iter, newiter;
	if (_e2_edftdlg_find_selected (rt, &iter))
		gtk_tree_store_insert_after (rt->stores[rt->currentID], &newiter, NULL, &iter);
	else
		gtk_tree_store_append (rt->stores[rt->currentID], &newiter, NULL);
	GtkTreeSelection *sel = gtk_tree_view_get_selection
		(GTK_TREE_VIEW (rt->views[rt->currentID]));
	if (rt->currentID == E2_CATPANE)
	{
		//provide a token category name
		gtk_tree_store_set (rt->stores[E2_CATPANE], &newiter, 0, _("new"), -1);
		 //create empty framework for the new category
#ifdef USE_GTK2_10
		gtk_tree_store_insert_with_values (rt->stores[E2_CATPANE],
			&iter, &newiter, -1, 1, _C(13), -1);
		gtk_tree_store_insert_with_values (rt->stores[E2_CATPANE],
			&iter, &newiter, -1, 1, _C(6), -1);
#else
		//add extensions node
		gtk_tree_store_append (rt->stores[E2_CATPANE], &iter, &newiter);
		gtk_tree_store_set (rt->stores[E2_CATPANE], &iter, 1, _C(13), -1);
		//add commands node
		gtk_tree_store_append (rt->stores[E2_CATPANE], &iter, &newiter);
		gtk_tree_store_set (rt->stores[E2_CATPANE], &iter, 1, _C(6), -1);
#endif
		GtkTreePath *path =
			gtk_tree_model_get_path (rt->typeset->ex.tree.model, &newiter);
		gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]),
			path, NULL, FALSE, 0, 0);
		gtk_tree_selection_select_path (sel, path);
		_e2_edftdlg_save_current_category (rt);
		_e2_edftdlg_update_category (path, rt);	//update rt->catpath & clear the views
		gtk_tree_path_free (path);
	}
	else	//not a new category
	{
//		gtk_list_store_set (rt->stores[[rt->currentID]], &newiter, 0, _("new"), -1);
		gtk_tree_selection_unselect_all (sel);
		gtk_tree_selection_select_iter (sel, &newiter);
	}
}
/**
@brief dialog response callback

@param dialog the dialog where the response was generated
@param response the response assigned to @a button
@param rt pointer to data struct for the dialog

@return
*/
static void _e2_edftdlg_response_cb (GtkDialog *dialog, gint response,
	E2_FileTypeDlgRuntime *rt)
{
	GtkTreeIter iter;
	switch (response)
	{
		case GTK_RESPONSE_APPLY:
			_e2_edftdlg_save_current_category (rt); //this might be dirty
			e2_option_tree_unbackup (rt->typeset, FALSE);	//get rid of old backup data
			e2_filetype_apply_allnew ();	//convert config data to runtime format
			e2_option_tree_backup (rt->typeset);	//get fresh backup data
			break;
		case GTK_RESPONSE_OK:
			_e2_edftdlg_save_current_category (rt); //this might be dirty
			e2_option_tree_unbackup (rt->typeset, FALSE);	//get rid of old backup data
			g_hash_table_destroy (rt->buffer_hash);
			g_free (rt);
			gtk_widget_destroy (filetypes_dialog);
			filetypes_dialog = NULL;
			e2_filetype_apply_allnew ();
			gtk_widget_grab_focus (curr_view->treeview);
			break;
		case E2_RESPONSE_USER1:
			//move-up button pressed
			if (_e2_edftdlg_find_selected (rt, &iter))
			{
				GtkTreePath *path = gtk_tree_model_get_path
					(GTK_TREE_MODEL (rt->stores[rt->currentID]), &iter);
				if (gtk_tree_path_prev (path))
				{
					GtkTreeIter previter;
					gtk_tree_model_get_iter (GTK_TREE_MODEL (rt->stores[rt->currentID]),
						&previter, path);
					gtk_tree_store_swap (rt->stores[rt->currentID], &iter, &previter);
				}
				gtk_tree_path_free (path);
			}
			break;
		case E2_RESPONSE_USER2:
			//move-down button pressed
			if (_e2_edftdlg_find_selected (rt, &iter))
			{
				GtkTreeIter nextiter = iter;
				if (gtk_tree_model_iter_next
					(GTK_TREE_MODEL (rt->stores[rt->currentID]), &nextiter))
						gtk_tree_store_swap (rt->stores[rt->currentID], &iter, &nextiter);
			}
			break;
		case E2_RESPONSE_USER3:
			//insert-after button pressed
			_e2_edftdlg_insert_row (rt);
			break;
		case E2_RESPONSE_REMOVE:
			if (rt->currentID == E2_CATPANE)
			{
				if (_e2_edftdlg_find_selected (rt, &iter))
				{
					GtkTreePath *newpath = gtk_tree_model_get_path
							(GTK_TREE_MODEL (rt->stores[E2_CATPANE]), &iter);
					GtkTreeIter newiter = iter;	//this is where we goto after the removal
					//(if there's a next iter, when the current is deleted, next becomes current)
					gboolean allgone = !
					(gtk_tree_model_iter_next (rt->typeset->ex.tree.model, &newiter)
						|| gtk_tree_path_prev (newpath));

					//this deletes all children too
					gtk_tree_store_remove (rt->stores[E2_CATPANE], &iter);

					if (allgone)
					{	//nowhere else to show
						if (rt->catpath != NULL)
							gtk_tree_path_free (rt->catpath);
						rt->catpath = NULL;
						//clear the related views
						gtk_tree_store_clear (rt->stores[E2_EXTPANE]);
						gtk_tree_store_clear (rt->stores[E2_CMDPANE]);
					}
					else
					{
						gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]),
							newpath, NULL, FALSE, 0, 0);
						GtkTreeSelection *sel =
							gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CATPANE]));
						gtk_tree_selection_select_path (sel, newpath);
						_e2_edftdlg_update_category (newpath, rt);
					}
					gtk_tree_path_free (newpath);
				}

			}
			else
				e2_tree_delete (GTK_TREE_VIEW (rt->views[rt->currentID]));
			break;
		default:
			_e2_edftdlg_cancel_cb (NULL, rt);
			break;
	}
	return;
}
/**
series of context-menu callback functions

@param item the selected menu item widget
@param rt pointer to dialog data struct

@return
*/
static void _e2_edftdlg_menu_copy_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]);
	GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
	if (gtk_tree_selection_count_selected_rows (sel) > 0)
	{
		GList *copied = e2_tree_copy (treeview);
		gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID);
		printd (DEBUG, "adding to buffer: key %s and %d rows", id_string,
			g_list_length (copied));
		g_hash_table_replace (rt->buffer_hash, id_string, copied);
	}
}
static void _e2_edftdlg_menu_cut_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]);
	GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
	if (gtk_tree_selection_count_selected_rows (sel) > 0)
	{
		_e2_edftdlg_menu_copy_cb (item, rt);
		e2_tree_delete (treeview);
	}
}
static void _e2_edftdlg_menu_paste_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	//as a special case, if the store is empty, setup to add the data
	gboolean empty = (gtk_tree_model_iter_n_children
		(GTK_TREE_MODEL (rt->stores[rt->currentID]), NULL) == 0);
	if (empty)
		_e2_edftdlg_insert_row (rt);

	GtkTreeView *treeview = GTK_TREE_VIEW (rt->views[rt->currentID]);
	//we paste after the 1st selected item
	GtkTreeSelection *sel = gtk_tree_view_get_selection (treeview);
	if (gtk_tree_selection_count_selected_rows (sel) > 0)
	{
		gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID);
		//get the buffered data, if any, for the current page
		GList *copied = g_hash_table_lookup (rt->buffer_hash, id_string);
		if (copied != NULL)
			e2_tree_paste (copied, treeview);
		g_free (id_string);
	}

	if (empty)
		e2_tree_delete (treeview);
}
static void _e2_edftdlg_menu_add_below_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	_e2_edftdlg_insert_row (rt);
}
static void _e2_edftdlg_menu_move_up_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	_e2_edftdlg_response_cb (GTK_DIALOG (filetypes_dialog), E2_RESPONSE_USER1, rt);
}
static void _e2_edftdlg_menu_move_down_cb (GtkWidget *item, E2_FileTypeDlgRuntime *rt)
{
	_e2_edftdlg_response_cb (GTK_DIALOG (filetypes_dialog), E2_RESPONSE_USER2, rt);
}
/**
@brief set popup menu position

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

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

@return
*/
static void _e2_edftdlg_set_menu_position (GtkMenu *menu,
	gint *x, gint *y, gboolean *push_in, GtkWidget *treeview)
{
	gint left, top;
	gtk_window_get_position (GTK_WINDOW (filetypes_dialog), &left, &top);
	GtkAllocation alloc = treeview->allocation;
	*x = left+ alloc.x + alloc.width/2;
	*y = top + alloc.y +alloc.height/2 - 30;
	*push_in = FALSE;
}
/**
@brief create treeview context menu

@param treeview widget where the action is to occur
@param button the clicked mouse button, or 0 for menu key
@param time event time
@param rt pointer to dialog data struct

@return
*/
static void _e2_edftdlg_menu_create (GtkWidget *treeview,
	guint button, guint32 time, E2_FileTypeDlgRuntime *rt)
{
	GtkWidget *menu = gtk_menu_new ();
	e2_menu_add (menu, _("Cu_t"), GTK_STOCK_CUT,
		_("Cut selected row(s)"), _e2_edftdlg_menu_cut_cb, rt);
	e2_menu_add (menu, _("_Copy"), GTK_STOCK_COPY,
		_("Copy selected row(s)"), _e2_edftdlg_menu_copy_cb, rt);
	//check for valid stored content
	gchar *id_string = g_strdup_printf (E2_FTDLG_SIG, rt->currentID);
	gboolean state = (g_hash_table_lookup (rt->buffer_hash, id_string) != NULL);
	g_free (id_string);
	GtkWidget *item = e2_menu_add (menu, _("_Paste"), GTK_STOCK_PASTE,
		_("Paste previously copied or cut row(s) after current row"),
		_e2_edftdlg_menu_paste_cb, rt);
	gtk_widget_set_sensitive (item, state);
	e2_menu_add_separator (menu);
	e2_menu_add (menu, _("_Add"), GTK_STOCK_ADD,
		_("Add a row after the current one"), _e2_edftdlg_menu_add_below_cb, rt);
	e2_menu_add_separator (menu);
	e2_menu_add (menu, _("_Up"), GTK_STOCK_GO_UP,
		_("Move selected row up"), _e2_edftdlg_menu_move_up_cb, rt);
	e2_menu_add (menu, _("_Down"), GTK_STOCK_GO_DOWN,
		_("Move selected row down"), _e2_edftdlg_menu_move_down_cb, rt);

	if (button == 0)
		//this was a menu-key press
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
		(GtkMenuPositionFunc) _e2_edftdlg_set_menu_position, treeview, 0, time);
	else
		//this was a button-3 click
		gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
}
/**
@brief treeview menu-button press callback

@param treeview the focused widget when the menu button was pressed
@param rt pointer to data struct for the dialog

@return TRUE
*/
static gboolean _e2_edftdlg_popup_menu_cb (GtkWidget *treeview,
	E2_FileTypeDlgRuntime *rt)
{
	guint32 event_time = gtk_get_current_event_time ();
	_e2_edftdlg_menu_create (treeview, 0, event_time, rt);
	return TRUE;
}
/**
@brief treeview button press callback

@param treeview the clicked treeview widget
@param event ptr to event data struct
@param rt pointer to data struct for the dialog

@return FALSE
*/
//FIXME find a way to cancel editing of a renderer that is clicked again
static gboolean _e2_edftdlg_button_press_cb (GtkWidget *treeview,
	GdkEventButton *event, E2_FileTypeDlgRuntime *rt)
{
	if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
	{
		GtkTreePath *path;
		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview),
				event->x, event->y, &path, NULL, NULL, NULL))
		{
			if (treeview == rt->views[E2_CATPANE])
			{
				_e2_edftdlg_save_current_category (rt);
				_e2_edftdlg_update_category (path, rt);
			}
			gtk_tree_path_free (path);
		}
		//clicked a bare part of the view
		if (treeview == rt->views[E2_CATPANE])
			rt->currentID = E2_CATPANE;
		else if (treeview == rt->views[E2_EXTPANE])
			rt->currentID = E2_EXTPANE;
		else
			rt->currentID = E2_CMDPANE;
	}
	else if (event->button == 3)
	{
		_e2_edftdlg_menu_create (treeview, 3, event->time, rt);
		return TRUE;
	}
	return FALSE;
}
/**
@brief abandon the dialog without making any change

@param widget the widget which was activated to trigger the cancel
@param rt pointer to data struct for the dialog

@return TRUE always - prevent other handlers
*/
static gboolean _e2_edftdlg_cancel_cb (GtkWidget *widget, E2_FileTypeDlgRuntime *rt)
{
	gtk_widget_destroy (filetypes_dialog);
	filetypes_dialog = NULL;
	e2_option_tree_unbackup (rt->typeset, TRUE);	//undo everything
	g_free (rt);
	gtk_widget_grab_focus (curr_view->treeview);
	return TRUE;
}
/**
@brief if Escape key is pressed, abandon the dialog without making any change

@param widget the widget which was activated to trigger the cancel
@parm event pointer to event data struct
@param rt pointer to data struct for the dialog

@return TRUE if Esc key processed, else FALSE
*/
static gboolean _e2_edftdlg_key_press_cb (GtkWidget *widget, GdkEventKey *event,
	E2_FileTypeDlgRuntime *rt)
{
	if (event->keyval == GDK_Escape)
	  //restore tree data and clean up
		return (_e2_edftdlg_cancel_cb (NULL, rt));

	return FALSE;
}
/**
@brief create and show edit filetypes dialog
@param open_category name of category to focus when dialog opens, or NULL
@return
*/
void e2_filetype_dialog_edit_create (gchar *open_category)
{
	//check if there is already one of these dialogs opened
	if (filetypes_dialog != NULL)
	{
		gtk_window_present (GTK_WINDOW (filetypes_dialog));
		return;
	}
	E2_OptionSet *set = e2_option_get_simple ("filetypes");
	if (set == NULL)
		return;	//can't do anything

	E2_FileTypeDlgRuntime *rt = ALLOCATE (E2_FileTypeDlgRuntime);
	CHECKALLOCATEDWARN (rt, return);
	rt->typeset = set;
	rt->catpath = NULL;
	//receptacle for cut/copied row(s) data generated from context menu
	rt->buffer_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
		g_free, (GDestroyNotify) e2_option_tree_menu_hash_clean);

	filetypes_dialog = e2_dialog_create (NULL, NULL, _("edit filetypes"),
		_e2_edftdlg_response_cb, rt);

	e2_option_connect (filetypes_dialog, FALSE);

	//main scrolled window
	GtkWidget *sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
		GTK_SHADOW_IN);
//	gtk_scrolled_window_set_placement (GTK_SCROLLED_WINDOW (sw),
//		e2_option_int_get ("scrollbar-position"));
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (filetypes_dialog)->vbox), sw);
	gtk_widget_set_size_request (sw, 0, 400);
	gtk_widget_show (sw);

	GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
	gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), hbox);
	gtk_viewport_set_shadow_type (GTK_VIEWPORT (GTK_BIN (sw)->child),
		GTK_SHADOW_NONE);
	gtk_widget_show (hbox);

	//categories scrolled window
	GtkWidget *cat_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
		GTK_SHADOW_IN);
	gtk_box_pack_start (GTK_BOX (hbox), cat_sw, TRUE, TRUE, 0);
	//categories store and treeview
	rt->stores[E2_CATPANE] = GTK_TREE_STORE (set->ex.tree.model);
	rt->views[E2_CATPANE] = gtk_tree_view_new_with_model (set->ex.tree.model);
	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_CATPANE]), TRUE);
#ifdef USE_GTK2_10
	gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (rt->views[E2_CATPANE]), TRUE);
#endif
	g_signal_connect (rt->views[E2_CATPANE], "popup-menu",
		G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt);
	GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
	g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (-1));	//flag for categories renderer
	g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt);
	gtk_tree_view_insert_column_with_attributes
		(GTK_TREE_VIEW (rt->views[E2_CATPANE]), -1, _("Categories"), renderer, "text", 0, NULL);
	//hide the tree expanders
#ifdef USE_GTK2_12
	gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (rt->views[E2_CATPANE]), FALSE);
#else
	GtkTreeViewColumn *expander_col = gtk_tree_view_column_new ();
	gtk_tree_view_insert_column (GTK_TREE_VIEW (rt->views[E2_CATPANE]), expander_col, 1);
	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (rt->views[E2_CATPANE]), expander_col);
	gtk_tree_view_column_set_visible (expander_col, FALSE);
#endif
	gtk_container_add (GTK_CONTAINER (cat_sw), rt->views[E2_CATPANE]);
	gtk_widget_set_size_request (cat_sw, 150, 0);
	gtk_widget_show_all (cat_sw);

	//extensions scrolled window
	GtkWidget *ext_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
		GTK_SHADOW_IN);
	gtk_box_pack_start (GTK_BOX (hbox), ext_sw, TRUE, TRUE, 0);
	//extensions liststore and treeview
	//store has 1 column, for displayed extensions
	rt->stores[E2_EXTPANE] = gtk_tree_store_new (1, G_TYPE_STRING);
	rt->views[E2_EXTPANE] = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->stores[E2_EXTPANE]));
	g_object_unref (rt->stores[E2_EXTPANE]);	//destroy store with view
	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_EXTPANE]), TRUE);
	g_signal_connect (rt->views[E2_EXTPANE], "popup-menu",
		G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt);
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
	g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (0));
	g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt);
	gtk_tree_view_insert_column_with_attributes
		(GTK_TREE_VIEW (rt->views[E2_EXTPANE]), -1, _("Extensions"), renderer, "text", 0, NULL);
	gtk_container_add (GTK_CONTAINER (ext_sw), rt->views[E2_EXTPANE]);
	gtk_widget_set_size_request (ext_sw, 100, 0);
	gtk_widget_show_all (ext_sw);
	GtkTreeSelection *selection = gtk_tree_view_get_selection
		(GTK_TREE_VIEW (rt->views[E2_EXTPANE]));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	//commands scrolled window
	GtkWidget *cmd_sw = e2_widget_get_sw (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC,
		GTK_SHADOW_IN);
	gtk_box_pack_start (GTK_BOX (hbox), cmd_sw, TRUE, TRUE, 0);
	//commands liststore and treeview
	//store has columns for displayed label, command
	rt->stores[E2_CMDPANE] = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
	rt->views[E2_CMDPANE] = gtk_tree_view_new_with_model (GTK_TREE_MODEL (rt->stores[E2_CMDPANE]));
	g_object_unref (rt->stores[E2_CMDPANE]);	//destroy store with view
	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), TRUE);
	g_signal_connect (rt->views[E2_CMDPANE], "popup-menu",
		G_CALLBACK (_e2_edftdlg_popup_menu_cb), rt);
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
	g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (0));
	g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt);
	gtk_tree_view_insert_column_with_attributes
		(GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 0, _("Labels"), renderer, "text", 0, NULL);
	GtkTreeViewColumn *column =
	gtk_tree_view_get_column (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 0);
	g_object_set (G_OBJECT (column), "resizable", TRUE, "min-width", 120, NULL);
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
	g_object_set_data (G_OBJECT (renderer), "col_num", GINT_TO_POINTER (1));
	g_signal_connect (G_OBJECT (renderer), "edited", G_CALLBACK (_e2_edftdlg_cell_edited_cb), rt);
	gtk_tree_view_insert_column_with_attributes
		(GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 1, _("Commands"), renderer, "text", 1, NULL);
	column = gtk_tree_view_get_column (GTK_TREE_VIEW (rt->views[E2_CMDPANE]), 1);
	g_object_set (G_OBJECT (column), "resizable", TRUE, NULL);

	gtk_container_add (GTK_CONTAINER (cmd_sw), rt->views[E2_CMDPANE]);
	gtk_widget_set_size_request (cmd_sw, 400, 0);
	gtk_widget_show_all (cmd_sw);
	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CMDPANE]));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	e2_option_tree_backup (set);	//keep a backup in case user cancels
	//fill the stores
	GtkTreePath *path;
	GtkTreeIter iter;
	if (open_category != NULL
		&& e2_tree_find_iter_from_str (set->ex.tree.model, 0, open_category, &iter, FALSE))
	{
		path = gtk_tree_model_get_path (set->ex.tree.model, &iter);
		gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (rt->views[E2_CATPANE]),
				path, NULL, FALSE, 0, 0);
		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CATPANE]));
		gtk_tree_selection_select_path (selection, path);
		_e2_edftdlg_update_category (path, rt);
		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (rt->stores[E2_CMDPANE]), &iter))
		{
			selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (rt->views[E2_CMDPANE]));
			gtk_tree_selection_select_iter (selection, &iter);
		}
		gtk_widget_grab_focus (rt->views[E2_CMDPANE]);
	}
	else if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter))
	{
		path = gtk_tree_model_get_path (set->ex.tree.model, &iter);
		_e2_edftdlg_update_category (path, rt);
	}

	//handle things
	g_signal_connect (G_OBJECT (filetypes_dialog), "key-press-event",
		G_CALLBACK (_e2_edftdlg_key_press_cb), rt);
//	g_signal_connect (G_OBJECT (filetypes_dialog), "delete-event",
//		G_CALLBACK (_e2_edftdlg_cancel_cb), rt);

	g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "button-press-event",
		G_CALLBACK (_e2_edftdlg_button_press_cb), rt);
	g_signal_connect (G_OBJECT (rt->views[E2_EXTPANE]), "button-press-event",
		G_CALLBACK (_e2_edftdlg_button_press_cb), rt);
	g_signal_connect (G_OBJECT (rt->views[E2_CMDPANE]), "button-press-event",
		G_CALLBACK (_e2_edftdlg_button_press_cb), rt);

//	g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "cursor-changed",
//		G_CALLBACK (_e2_edftdlg_cat_cursor_move_cb), rt);
	selection = gtk_tree_view_get_selection
		(GTK_TREE_VIEW (rt->views[E2_CATPANE]));
	g_signal_connect (G_OBJECT (selection), "changed",
		G_CALLBACK (_e2_edftdlg_cat_sel_change_cb), rt);
	//traps for pane-changes by tab key or mouse click
	g_signal_connect (G_OBJECT (rt->views[E2_CATPANE]), "focus-in-event",
		G_CALLBACK (_e2_edftdlg_focus_in_cb), rt);
	g_signal_connect (G_OBJECT (rt->views[E2_EXTPANE]), "focus-in-event",
		G_CALLBACK (_e2_edftdlg_focus_in_cb), rt);
	g_signal_connect (G_OBJECT (rt->views[E2_CMDPANE]), "focus-in-event",
		G_CALLBACK (_e2_edftdlg_focus_in_cb), rt);

	//add buttons, custom and standard
	E2_Button buttontype;
	buttontype.label = _("_Up");
	buttontype.stock = GTK_STOCK_GO_UP;
	buttontype.response = E2_RESPONSE_USER1;
	e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype,
		_("Move the selected row one place up"), NULL, NULL);

	buttontype.label = _("_Down");
	buttontype.stock = GTK_STOCK_GO_DOWN;
	buttontype.response = E2_RESPONSE_USER2;
	e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype,
		_("Move the selected row one place down"), NULL, NULL);

	buttontype.label = _("Add a_fter");
	buttontype.stock = GTK_STOCK_ADD;
	buttontype.response = E2_RESPONSE_USER3;
	e2_dialog_add_button_custom (filetypes_dialog, FALSE, &buttontype,
		_("Add a row after the currently selected one"), NULL, NULL);

	E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
	E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;
	e2_dialog_show (filetypes_dialog, app.main_window, 0,
		&E2_BUTTON_REMOVE, &E2_BUTTON_CANCEL,
		&E2_BUTTON_APPLY, &E2_BUTTON_OK, NULL);
}

#endif	//ndef OLDFTDLG

  /***************************/
 /**** what-to-do dialog ****/
/***************************/

typedef struct _E2_FileTypeDlg2Runtime
{
	GtkWidget *view_btn;
	GtkWidget *open_btn;
	gchar *utfpath;	//utf8 absolute path of item
	gint processed;	//store for return-code
} E2_FileTypeDlg2Runtime;

/**
@brief unknown filetype dialog response-signal callback

@param dialog the dialog where the response was triggered
@param response the number assigned the activated widget
@param view rt data for the dialog

@return
*/
static void _e2_filetype_dialog_response_cb (GtkDialog *dialog, gint response,
	E2_FileTypeDlg2Runtime *rt)
{
	printd (DEBUG, "response_cb (dialog:_,response:%d,rt:_)", response);
	gtk_widget_destroy (GTK_WIDGET (dialog));
	gchar *action;
	//remember this when rt is cleared at end of dialog func, and handle any whitespace
	gchar *item = g_strconcat ("\"", rt->utfpath, "\"", NULL);
	//send back "handled" type flag to caller
	switch (response)
	{
		case E2_RESPONSE_USER1:	//edit
		case E2_RESPONSE_USER2:	//view
		case E2_RESPONSE_USER3:	//open with
		case 124:	//exec
			rt->processed = E2_TYPE_HANDLED;
			break;
		case GTK_RESPONSE_ACCEPT:	//add
		case 125:	//open with default (back in the calling code)
			rt->processed = E2_TYPE_NOTHANDLED;
			break;
		default:	//this includes GTK_RESPONSE_DELETE_EVENT
			rt->processed = E2_TYPE_CANCELLED;
			break;
	}
	gtk_main_quit ();	//allow the dialog creation function to return

	switch (response)
	{
/* these callbacks handle a specific filename, so are usable for output
   pane selections as well as filelist selections
   item is a quouted absolute utf-8 path string */
		case GTK_RESPONSE_ACCEPT:	//add
			//open the filetypes config dialog with no extension selected
			e2_filetype_config_show (NULL);
			break;
		case E2_RESPONSE_USER1:	//edit
			action = g_strconcat (_A(5),".",_A(39), NULL);
			gdk_threads_leave ();	//open BGL
			e2_action_run_simple_from (action, item, NULL);
			gdk_threads_enter ();
			g_free (action);
			break;
		case E2_RESPONSE_USER2:	//view
			action = g_strconcat (_A(5),".",_A(101), NULL);
			gdk_threads_leave ();	//open BGL
			e2_action_run_simple_from (action, item, NULL);
			gdk_threads_enter ();
			g_free (action);
			break;
		case E2_RESPONSE_USER3:	//open with
			action = g_strconcat (_A(5),".",_A(62), NULL);
			gdk_threads_leave ();	//open BGL
			e2_action_run_simple_from (action, item, NULL);
			gdk_threads_enter ();
			g_free (action);
			break;
		case 124:	//execute
#ifdef E2_COMMANDQ
			e2_command_run (item, E2_COMMAND_RANGE_DEFAULT, FALSE);
#else
			e2_command_run (item, E2_COMMAND_RANGE_DEFAULT);
#endif
			break;
		default:
			break;
	}
	gtk_widget_grab_focus (curr_view->treeview);
	g_free (item);
}
/**
@brief create and show dialog for handling unknown or ambiguous filetype
@a newtype should be TRUE when @a filename does have an extension
@a newtype and @a ambig should not both be TRUE
@param localpath absolute or relative path of item to be processed, localised string
@param text TRUE to include view or edit option in the dialog
@param ambig TRUE to enable choice to execute or open item
@param newtype TRUE to include a button to add a new filetype

@return enumerator signalling how @a filename was "actioned" via the dialog
*/
gint e2_filetype_dialog_create (VPATH *localpath, gboolean text, gboolean ambig,
	gboolean newtype)
{
	gint retval;
	gboolean addpath;
	gchar *usepath, *dir, *base, *title, *public, *message;
	struct stat sb;

	E2_FileTypeDlg2Runtime *rt = ALLOCATE (E2_FileTypeDlg2Runtime);
	CHECKALLOCATEDWARN (rt, return);

	usepath = VPSTR (localpath);
//	printd (DEBUG, "e2_filetype_dialog_create: %s", usepath);
	addpath = !g_path_is_absolute (usepath);
	if (addpath)
	{
	//FIXME race (unlikely) if dir goes and cd happens, worse if past virtual root
#ifdef E2_VFS
		if (localpath->spacedata == other_view->spacedata)
			usepath = e2_utils_dircat (other_view, usepath, TRUE);
		else
#endif
			usepath = e2_utils_dircat (curr_view, usepath, TRUE);
	}

	rt->utfpath = D_FILENAME_FROM_LOCALE (usepath);
	title = (ambig) ? _("ambiguous filetype") : _("unrecognised filetype");
	public = g_markup_escape_text (rt->utfpath, -1);
	dir = g_path_get_dirname (public);
	base = g_path_get_basename (public);
	message = g_strdup_printf (
		_("What would you like to do with\n<b>%s</b>\nin %s ?"), base, dir);
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION,
		message, title, _e2_filetype_dialog_response_cb, rt);
	g_free (public);
	g_free (dir);
	g_free (base);
	g_free (message);

	//can always add to category, even if item does not presently exist
	if (newtype && !ambig)
		e2_dialog_add_undefined_button_custom (dialog, FALSE,
		GTK_RESPONSE_ACCEPT, _("_Add.."), "gtk-preferences",
		_("Create a new filetype for this extension, or add it to an existing filetype"),
		NULL, NULL);

#ifdef E2_VFS
	VPATH ddata = { usepath, localpath->spacedata };
	if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE()))
#else
	if (!e2_fs_stat (usepath, &sb E2_ERR_NONE()))
#endif
	{
		if (S_ISREG (sb.st_mode))
		{
			if (sb.st_size == 0)
				rt->view_btn = e2_dialog_add_undefined_button_custom (dialog, FALSE,
					E2_RESPONSE_USER1, _("_Edit"), "edit_"E2IP".png",
					_("Edit the file"), NULL, NULL);
			else if (text)
				rt->view_btn = e2_dialog_add_undefined_button_custom (dialog, FALSE,
					E2_RESPONSE_USER2, _("_View"), "view_"E2IP".png",
					_("Read the file"), NULL, NULL);
		}
		if (ambig)
		{
			e2_dialog_add_undefined_button_custom (dialog, FALSE,
				124, _("_Run"), GTK_STOCK_EXECUTE,
					_("Execute the item"), NULL, NULL);
			rt->open_btn = e2_dialog_add_undefined_button_custom (dialog, TRUE,
				125, _("_Open"), GTK_STOCK_OPEN,	//FIXME better icon
					_("Open with the default application"), NULL, NULL);
		}
		else
			rt->open_btn = e2_dialog_add_undefined_button_custom (dialog, TRUE,
				E2_RESPONSE_USER3, _("_Open.."), "open_with_"E2IP".png",
					_("Enter a command with which to open the file"), NULL, NULL);
		gtk_widget_grab_focus (rt->open_btn);
	}

	e2_dialog_show (dialog, NULL, 0, &E2_BUTTON_CANCEL, NULL);
	gtk_main ();	//block until user selects something
	if (addpath)
		g_free (usepath);
	retval = rt->processed;	//get code for what was selected
	DEALLOCATE (E2_FileTypeDlg2Runtime, rt);
	return retval;
}
