/* Popup menus for the Midnight Commander
 *
 * Copyright (C) 1998 The Free Software Foundation
 *
 * Authors: Federico Mena <federico@nuclecu.unam.mx>
 *          Miguel de Icaza <miguel@nuclecu.unam.mx>
 */


#include <config.h>
#include "global.h"
#include <gnome.h>
#include "panel.h"
#include "cmd.h"
#include "dialog.h"
#include "ext.h"
#include "gpageprop.h"
#include "gpopup.h"
#include "main.h"
#include "gnome-file-property-dialog.h"
#define CLIST_FROM_SW(panel_list) GTK_CLIST (GTK_BIN (panel_list)->child)


extern int we_can_afford_the_speed;

/* Flags for the popup menu entries.  They specify to which kinds of files an entry is valid for. */
enum {
	F_ALL 		= 1 << 0,	/* Applies to all files */
	F_REGULAR	= 1 << 1,	/* Applies only to regular files */
	F_SYMLINK	= 1 << 2,	/* Applies only to symlinks */
	F_SINGLE	= 1 << 3,	/* Applies only to a single file, not to a multiple selection */
	F_NOTDIR	= 1 << 4,	/* Applies to non-directories */
	F_DICON		= 1 << 5,	/* Applies to desktop icons */
	F_PANEL		= 1 << 6,       /* Applies to files from a panel window */
	F_MOUNTABLE     = 1 << 7,       /* Only if the device is mountable */
	F_UNMOUNTABLE   = 1 << 8,       /* Only if the device is unmountable */
	F_EJECTABLE     = 1 << 9,       /* Only if the device is ejectable */
	F_LOCAL         = 1 << 10       /* File must be on a local file system */
};

struct action {
	char *text;		/* Menu item text */
	int flags;		/* Flags from the above enum */
	GtkSignalFunc callback;	/* Callback for menu item */
};


static void
panel_action_open_with (GtkWidget *widget, WPanel *panel)
{
	char *command;
	
	command = input_expand_dialog (_(" Open with..."),
				       _("Enter extra arguments:"), panel->dir.list [panel->selected].fname);
	if (!command)
		return;
	execute (command);
	 g_free (command);
}

static void
panel_action_open (GtkWidget *widget, WPanel *panel)
{
	if (do_enter (panel))
		return;
	panel_action_open_with (widget, panel);
}

static void
panel_action_view (GtkWidget *widget, WPanel *panel)
{
	view_cmd (panel);
}

static void
panel_action_view_unfiltered (GtkWidget *widget, WPanel *panel)
{
	view_simple_cmd (panel);
}

static void
panel_action_edit (GtkWidget *widget, WPanel *panel)
{
	char *full = g_concat_dir_and_file (panel->cwd, selection (panel)->fname);
	
	gmc_edit (full);
	g_free (full);
}

/* Pops up the icon properties pages */
static void
dicon_properties (GtkWidget *widget, DesktopIconInfo *dii)
{
	int retval = 1;
	char *path;
	GtkWidget *dlg;
	int run;
	
	path = concat_dir_and_file (desktop_directory, dii->filename);
/*	retval = item_properties (dii->dicon, path, dii);*/
	dlg = gnome_file_property_dialog_new (path, TRUE);
	gtk_widget_show_all (dlg);
	run = gnome_dialog_run (GNOME_DIALOG (dlg));
	if (run == 0)
		retval = gnome_file_property_dialog_make_changes (GNOME_FILE_PROPERTY_DIALOG (dlg));

	if (run != -1)
		gtk_widget_destroy (dlg);
	g_free(path);

	if (retval)
		reread_cmd ();
}

/* Executes a desktop icon */
static void
dicon_execute (GtkWidget *widget, DesktopIconInfo *dii)
{
	desktop_icon_info_open (dii);
}

static void
dicon_unmount (GtkWidget *widget, DesktopIconInfo *dii)
{
	char *full = g_concat_dir_and_file (desktop_directory, dii->filename);
	
	do_mount_umount (full, FALSE);
	g_free (full);
}

static void
dicon_mount (GtkWidget *widget, DesktopIconInfo *dii)
{
	char *full = g_concat_dir_and_file (desktop_directory, dii->filename);
	
	do_mount_umount (full, TRUE);
	g_free (full);
}

static void
dicon_eject (GtkWidget *widget, DesktopIconInfo *dii)
{
	char *full = g_concat_dir_and_file (desktop_directory, dii->filename);
	
	do_mount_umount (full, FALSE);
	do_eject (full);
	g_free (full);
}

static void
panel_action_properties (GtkWidget *widget, WPanel *panel)
{
	gint retval;
	file_entry *fe = &panel->dir.list [panel->selected];
	char *full_name = concat_dir_and_file (panel->cwd, fe->fname);
	GtkWidget *dlg;
	int run;

/*	if (item_properties (GTK_WIDGET (CLIST_FROM_SW (panel->list)), full_name, NULL) != 0)
	reread_cmd ();*/
	dlg = gnome_file_property_dialog_new (full_name, we_can_afford_the_speed);
	gnome_dialog_set_parent (GNOME_DIALOG (dlg), GTK_WINDOW (panel->xwindow));

	run = gnome_dialog_run (GNOME_DIALOG (dlg));
	
	if (run == 0)
		retval = gnome_file_property_dialog_make_changes (GNOME_FILE_PROPERTY_DIALOG (dlg));

	if (run != -1)
		gtk_widget_destroy (dlg);
	 g_free (full_name);
	if (retval)
		reread_cmd ();
}

static void
dicon_move (GtkWidget *widget, DesktopIconInfo *dii)
{
	g_warning ("Implement this function!");
}

static void
dicon_copy (GtkWidget *widget, DesktopIconInfo *dii)
{
	g_warning ("Implement this function!");
}

static void
dicon_delete (GtkWidget *widget, DesktopIconInfo *dii)
{
	desktop_icon_info_delete (dii);
}

/* This is our custom signal connection function for popup menu items -- see below for the
 * marshaller information.  We pass the original callback function as the data pointer for the
 * marshaller (uiinfo->moreinfo).
 */
static void
popup_connect_func (GnomeUIInfo *uiinfo, gchar *signal_name, GnomeUIBuilderData *uibdata)
{
	g_assert (uibdata->is_interp);

	if (uiinfo->moreinfo) {
		gtk_object_set_data (GTK_OBJECT (uiinfo->widget), "popup_user_data", uiinfo->user_data);
		gtk_signal_connect_full (GTK_OBJECT (uiinfo->widget), signal_name,
					 NULL,
					 uibdata->relay_func,
					 uiinfo->moreinfo,
					 uibdata->destroy_func,
					 FALSE,
					 FALSE);
	}
}

/* Our custom marshaller for menu items.  We need it so that it can extract the per-attachment
 * user_data pointer from the parent menu shell and pass it to the callback.  This overrides the
 * user-specified data from the GnomeUIInfo structures.
 */

typedef void (* ActivateFunc) (GtkObject *object, gpointer data);

static void
popup_marshal_func (GtkObject *object, gpointer data, guint n_args, GtkArg *args)
{
	ActivateFunc func;
	gpointer user_data;

	func = (ActivateFunc) data;
	user_data = gtk_object_get_data (object, "popup_user_data");

	gtk_object_set_data (GTK_OBJECT (GTK_WIDGET (object)->parent), "popup_active_item", object);
	(* func) (object, user_data);
}

/* Fills the menu with the specified uiinfo at the specified position, using our magic marshallers
 * to be able to fetch the active item.  The code is shamelessly ripped from gnome-popup-menu.
 */
static void
fill_menu (GtkMenuShell *menu_shell, GnomeUIInfo *uiinfo, int pos)
{
	GnomeUIBuilderData uibdata;

	/* We use our own callback marshaller so that it can fetch the popup user data
	 * from the popup menu and pass it on to the user-defined callbacks.
	 */

	uibdata.connect_func = popup_connect_func;
	uibdata.data = NULL;
	uibdata.is_interp = TRUE;
	uibdata.relay_func = popup_marshal_func;
	uibdata.destroy_func = NULL;

	gnome_app_fill_menu_custom (menu_shell, uiinfo, &uibdata, NULL, FALSE, pos);
}


/* The context menu: text displayed, condition that must be met and the routine that gets invoked
 * upon activation.
 */
static struct action file_actions[] = {
	{ N_("Properties"),      F_SINGLE | F_PANEL,   	  	 (GtkSignalFunc) panel_action_properties },
	{ N_("Delete"),          F_PANEL | F_ALL,                (GtkSignalFunc) delete_cmd },
	{ N_("Delete"),          F_DICON | F_ALL,                (GtkSignalFunc) dicon_delete },
	{ N_("Properties"),      F_SINGLE | F_DICON,  	  	 (GtkSignalFunc) dicon_properties },
	{ N_("Mount device"),    F_SINGLE|F_MOUNTABLE|F_DICON,   (GtkSignalFunc) dicon_mount },
	{ N_("Unmount device"),  F_SINGLE|F_UNMOUNTABLE|F_DICON, (GtkSignalFunc) dicon_unmount },
	{ N_("Eject device"),    F_SINGLE|F_EJECTABLE|F_DICON,   (GtkSignalFunc) dicon_eject },
	{ "",                    F_SINGLE,   	    	  	 NULL },
	{ N_("Open"),            F_LOCAL| F_PANEL | F_ALL,	 (GtkSignalFunc) panel_action_open },
	{ N_("Open"),            F_LOCAL | F_DICON | F_ALL, 	 (GtkSignalFunc) dicon_execute },
	{ N_("Open with"),       F_LOCAL | F_PANEL | F_ALL, 	 (GtkSignalFunc) panel_action_open_with },
	{ N_("View"),            F_PANEL | F_NOTDIR,      	 (GtkSignalFunc) panel_action_view },
	{ N_("View unfiltered"), F_PANEL | F_NOTDIR,      	 (GtkSignalFunc) panel_action_view_unfiltered },  
	{ N_("Edit"),            F_PANEL | F_NOTDIR,             (GtkSignalFunc) panel_action_edit },
	{ "",                    0,          	                 NULL },
	{ N_("Move/rename..."),  F_PANEL | F_ALL,                (GtkSignalFunc) ren_cmd },
	{ N_("Copy..."),         F_PANEL | F_ALL,                (GtkSignalFunc) copy_cmd },
	{ "",                    0,          	                 NULL },
	{ N_("Link..."),         F_PANEL | F_REGULAR | F_SINGLE, (GtkSignalFunc) link_cmd },
	{ N_("Symlink..."),      F_PANEL | F_SINGLE,             (GtkSignalFunc) symlink_cmd },
	{ N_("Edit symlink..."), F_PANEL | F_SYMLINK,            (GtkSignalFunc) edit_symlink_cmd },
	{ NULL, 0, NULL },
};


/* Creates the menu items for the standard actions.  Returns the position at which additional menu
 * items should be inserted.
 */
static int
create_actions (GtkWidget *menu, WPanel *panel,
		DesktopIconInfo *dii,
		int panel_row, char *filename)
{
	gpointer closure;
	struct action *action;
	int pos;
	GnomeUIInfo uiinfo[] = {
		{ 0 },
		GNOMEUIINFO_END
	};

	closure = panel ? (gpointer) panel : (gpointer) dii;

	pos = 0;
	
	for (action = file_actions; action->text; action++) {
		/* First, try F_PANEL and F_DICON flags */

		if (!panel && (action->flags & F_PANEL))
			continue;

		if (!dii && (action->flags & F_DICON))
			continue;

		/* Items with F_ALL bypass any other condition */

		if (!(action->flags & F_ALL)) {
			/* Items with F_SINGLE require that ONLY ONE marked files exist */

			if (panel && (action->flags & F_SINGLE))
				if (panel->marked > 1)
					continue;

			/* Items with F_NOTDIR requiere that the selection is not a directory */

			if (panel && (action->flags & F_NOTDIR)) {
				struct stat *s = &panel->dir.list[panel_row].buf;

				if (panel->dir.list [panel_row].f.link_to_dir)
					continue;

				if (S_ISDIR (s->st_mode))
					continue;
			}

			/* Items with F_REGULAR do not accept any strange file types */

			if (panel && (action->flags & F_REGULAR)) {
				struct stat *s = &panel->dir.list[panel_row].buf;

				if (S_ISLNK (s->st_mode) && panel->dir.list[panel_row].f.link_to_dir)
					continue;

				if (S_ISSOCK (s->st_mode) || S_ISCHR (s->st_mode) ||
				    S_ISFIFO (s->st_mode) || S_ISBLK (s->st_mode))
					continue;
			}

			/* Items with F_SYMLINK only operate on symbolic links */

			if (panel && action->flags & F_SYMLINK) {
				struct stat *s = &panel->dir.list[panel_row].buf;

				if (!S_ISLNK (s->st_mode))
					continue;
			}

			if (action->flags & (F_MOUNTABLE|F_UNMOUNTABLE)){
				char *fullname;
				file_entry *fe;
				int v;
				int is_mounted;

				fullname = g_concat_dir_and_file (desktop_directory, dii->filename);
				fe = file_entry_from_file (fullname);
				if (fe){
					v = is_mountable (fullname, fe, &is_mounted, NULL);
					file_entry_free (fe);
					g_free (fullname);
					
					if (!v)
						continue;

					if (is_mounted && (action->flags & F_MOUNTABLE))
						continue;

					if ((!is_mounted) && (action->flags & F_UNMOUNTABLE))
						continue;
				} else {
					g_free (fullname);
					continue;
				}
			}

			if (action->flags & (F_EJECTABLE)){
				char *fullname;
				file_entry *fe;
				int v;
				int is_mounted;

				fullname = g_concat_dir_and_file (desktop_directory, dii->filename);
				fe = file_entry_from_file (fullname);
				if (fe){
					v = is_mountable (fullname, fe, &is_mounted, NULL);
					file_entry_free (fe);
					
					if (!v){
						g_free (fullname);
						continue;
					}

					if (!is_ejectable (fullname)){
						g_free (fullname);
						continue;
					}
					g_free (fullname);
					
				} else {
					g_free (fullname);
					continue;
				}
			}

		}

		/* Create the appropriate GnomeUIInfo structure for the menu item */

		if (action->text[0]) {
			uiinfo[0].type = GNOME_APP_UI_ITEM;
			uiinfo[0].label = _(action->text);
			uiinfo[0].hint = NULL;
			uiinfo[0].moreinfo = action->callback;
			uiinfo[0].user_data = closure;
			uiinfo[0].unused_data = NULL;
			uiinfo[0].pixmap_type = GNOME_APP_PIXMAP_NONE;
			uiinfo[0].pixmap_info = NULL;
			uiinfo[0].accelerator_key = 0;
			uiinfo[0].ac_mods = 0;
			uiinfo[0].widget = NULL;
		} else
			uiinfo[0].type = GNOME_APP_UI_SEPARATOR;

		fill_menu (GTK_MENU_SHELL (menu), uiinfo, pos);
		pos++;
	}

	return pos;
}

/* These functions activate a menu popup action for a filename in a hackish way, as they have to
 * fetch the action from the menu item's label.
 */

static char *
get_label_text (GtkMenuItem *item)
{
	GtkWidget *label;

	/* THIS DEPENDS ON THE LATEST GNOME-LIBS!  Keep this in sync with gnome-app-helper until we
	 * figure a way to do this in a non-hackish way.
	 */

	label = GTK_BIN (item)->child;
	g_assert (label != NULL);

	return GTK_LABEL (label)->label;
}

static void
mime_command_from_panel (GtkMenuItem *item, WPanel *panel)
{
	char *filename;
	char *action;
	int movedir;
	
	/*
	 * This is broken, but we dont mind.  Federico
	 * needs to explain me what was he intending here.
	 * panel->selected does not mean, it was the icon
	 * that got clicked.
	 */
}

static void
mime_command_from_desktop_icon (GtkMenuItem *item, char *filename)
{
	char *action, *buf;
	int movedir, size;
	char *key, *flag_key;
	const char *mime_type, *val, *flags;
	int needs_terminal = 0;

	action = get_label_text (item);
	
	key = gtk_object_get_user_data (GTK_OBJECT (item));
	mime_type = gnome_mime_type_or_default (filename, NULL);
	if (!mime_type)
		return;

	/*
	 * Find out if we need to run this in a terminal
	 */
	if (gnome_metadata_get (filename, "flags", &size, &buf) == 0){
		needs_terminal = strstr (flags, "needsterminal") != 0;
		g_free (buf);
	} else {
		flag_key = g_strconcat ("flags.", key, "flags", NULL);
		flags = gnome_mime_get_value (filename, flag_key);
		g_free (flag_key);
		if (flags)
			needs_terminal = strstr (flags, "needsterminal") != 0;
	}
	
	val = gnome_mime_get_value (mime_type, key);
	exec_extension (filename, val, NULL, NULL, 0, needs_terminal);
}

/* Create the menu items common to files from panel window and desktop icons, and also the items for
 * regexp_command() actions.
 */
static void
create_regexp_actions (GtkWidget *menu, WPanel *panel,
		       DesktopIconInfo *dii,
		       int panel_row, char *filename, int insert_pos)
{
	gpointer closure, regex_closure;
	int i;
	GtkSignalFunc regex_callback;
	const char *mime_type;
	GList *keys, *l;
	GnomeUIInfo uiinfo[] = {
		{ 0 },
		GNOMEUIINFO_END
	};

	regex_callback = mime_command_from_desktop_icon;
	if (panel) {
		closure = panel;
		regex_closure = filename;
	} else {
		closure = dii;
		regex_closure = filename;
	}

	/* Fill in the regex command part */

	mime_type = gnome_mime_type_or_default (filename, NULL);
	if (!mime_type)
		return;
	
	keys = gnome_mime_get_keys (mime_type);

	for (l = keys; l; l = l->next) {
		char *key = l->data;
		char *str;
		
		str = key;
		if (strncmp (key, "open.", 5) != 0)
			continue;

		str += 5;
		while (*str && *str != '.')
			str++;

		if (*str)
			str++;
		
		if (!*str)
			continue;

		/* Create the item for that entry */

		uiinfo[0].type = GNOME_APP_UI_ITEM;
		uiinfo[0].label = str;
		uiinfo[0].hint = NULL;
		uiinfo[0].moreinfo = regex_callback;
		uiinfo[0].user_data = regex_closure;
		uiinfo[0].unused_data = NULL;
		uiinfo[0].pixmap_type = GNOME_APP_PIXMAP_NONE;
		uiinfo[0].pixmap_info = NULL;
		uiinfo[0].accelerator_key = 0;
		uiinfo[0].ac_mods = 0;
		uiinfo[0].widget = NULL;

		fill_menu (GTK_MENU_SHELL (menu), uiinfo, insert_pos++);
		gtk_object_set_user_data (GTK_OBJECT (uiinfo [0].widget), key);
	}
	g_list_free (keys);
}

/* Convenience callback to exit the main loop of a modal popup menu when it is deactivated*/
static void
menu_shell_deactivated (GtkMenuShell *menu_shell, gpointer data)
{
	gtk_main_quit ();
}

/* Returns the index of the active item in the specified menu, or -1 if none is active */
static int
get_active_index (GtkMenu *menu)
{
	GList *l;
	GtkWidget *active;
	int i;

	active = gtk_object_get_data (GTK_OBJECT (menu), "popup_active_item");

	for (i = 0, l = GTK_MENU_SHELL (menu)->children; l; l = l->next, i++)
		if (active == l->data)
			return i;

	return -1;
}

/*
 * Create a context menu
 * It can take either a WPanel or a GnomeDesktopEntry.  One of them should
 * be set to NULL.
 */
int
gpopup_do_popup (GdkEventButton *event, WPanel *from_panel,
		 DesktopIconInfo *dii,
		 int panel_row, char *filename)
{
	GtkWidget *menu;
	int pos;
	guint id;

	g_return_val_if_fail (event != NULL, -1);
	g_return_val_if_fail (from_panel != NULL || dii != NULL || filename != NULL, -1);

	menu = gtk_menu_new ();

	/* Connect to the deactivation signal to be able to quit our
           modal main loop */
	id = gtk_signal_connect (GTK_OBJECT (menu), "deactivate",
				 (GtkSignalFunc) menu_shell_deactivated,
				 NULL);

	/* Fill the menu */

	pos = create_actions (menu, from_panel, dii,
			      panel_row, filename);
	create_regexp_actions (menu, from_panel, dii,
			       panel_row, filename, pos);

	/* Run it */

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time);
	gtk_grab_add (menu);
	gtk_main ();
	gtk_grab_remove (menu);

	gtk_signal_disconnect (GTK_OBJECT (menu), id);

	return get_active_index (GTK_MENU (menu));
}




