/*
** 1998-05-29 -	I wisen up, and design a generic command dialog interface. Might come in
**		handy. This will support the Opus-like OK, All, Skip and Cancel buttons
**		for all commands using it (i.e. delete, rename, protect etc). Great.
** 1998-06-04 -	Added flags to the cmd_generic() call, allowing different commands to
**		tailor the generic part somewhat. Specifically, that means that some
**		commands (rename) now can get rid of the "All" button.
** 1998-07-09 -	Added flag CGF_NODIRS, which avoids calling body() or action() on dirs.
**		Required by the split command.
** 1998-07-10 -	Added CGF_NOENTER/NOESC, which disable the new keyboard shortcuts.
** 1998-07-27 -	Renamed the CGF_NOENTER flag to CGF_NORETURN. Better.
** 1998-09-12 -	Implemented the CGF_SRC flag for source pane rescanning.
*/

#include "gentoo.h"

#include <gdk/gdkkeysyms.h>

#include "errors.h"
#include "fileutil.h"
#include "dirpane.h"

#include "cmd_generic.h"

struct gen_info {
	guint32		flags;

	MainInfo	*min;
	DirPane		*src, *dst;
	GSList		*s_slist;		/* Source pane selection. */
	GSList		*s_iter;		/* Source selection iterator. */
	gchar		old_path[PATH_MAX];

	GenBodyFunc	bf;
	GenActionFunc	af;
	GenFreeFunc	ff;
	gpointer	user;		/* User's data, passed on in callbacks. */
	gboolean	need_update;	/* Set if user's action function has been called. */

	GtkWidget	*dlg;
	GtkWidget	*body;
	GtkWidget	*ok, *all, *skip, *cancel;
};

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-29 -	Find the first selected entry, initialize body using it, and return 1.
**		If no selected entry was found, 0 is returned.
*/
static int first_body(struct gen_info *gen)
{
	for(gen->s_iter = gen->s_slist; gen->s_iter != NULL; gen->s_iter = g_slist_next(gen->s_iter))
	{
		if((gen->flags & CGF_NODIRS) && S_ISDIR(DP_SEL_LSTAT(gen->s_iter).st_mode))
			continue;
		if((gen->flags & CGF_LINKSONLY) && !(S_ISLNK(DP_SEL_LSTAT(gen->s_iter).st_mode)))
			continue;
		gen->bf(gen->min, gen->src, DP_SEL_ROW(gen->s_iter), &GTK_DIALOG(gen->dlg)->window, gen->user);
		return 1;
	}
	return 0;
}

/* 1998-05-29 -	This is run when user clicks "Cancel", or when we run out of entries to
**		work on. It simply closes everything down.
*/
static void end_command(struct gen_info *gen)
{
	GtkWidget	*dlg;

	if(gen->s_slist != NULL)
	{
		dp_free_selection(gen->s_slist);
		gen->s_slist = NULL;
	}

	if((dlg = gen->dlg) != NULL)		/* Protect against the evils of uncontrolled recursion. */
	{
		gen->dlg = NULL;
		if(gen->ff != NULL)
			gen->ff(gen->user);		/* Let caller free his stuff. */
		gtk_widget_destroy(dlg);
		fut_cd(gen->old_path, NULL, 0);
		if(gen->need_update)
		{
			if(gen->flags & CGF_SRC)
				dp_rescan_dir(gen->src);
			if(!(gen->flags & CGF_NODST))
				dp_rescan_dir(gen->dst);
			gen->need_update = FALSE;
		}
	}
	err_show(gen->min);
}

/* 1998-05-29 -	Find the next selected entry, and generate body using it. If none is
**		found, we terminate by calling end_command().
** 1999-03-05 -	Rewritten for new selection and general call format.
*/
static void next_or_end(struct gen_info *gen)
{
	for(gen->s_iter = g_slist_next(gen->s_iter); gen->s_iter != NULL; gen->s_iter = g_slist_next(gen->s_iter))
	{
		if((gen->flags & CGF_NODIRS) && S_ISDIR(DP_SEL_LSTAT(gen->s_iter).st_mode))
			continue;
		if((gen->flags & CGF_LINKSONLY) && !(S_ISLNK(DP_SEL_LSTAT(gen->s_iter).st_mode)))
			continue;
		gen->bf(gen->min, gen->src, DP_SEL_ROW(gen->s_iter), &GTK_DIALOG(gen->dlg)->window, gen->user);
		return;
	}
	end_command(gen);
}

/* 1998-05-29 -	Execute the command on all selected entries, from the current and
**		onwards. Then close down the dialog and exit.
*/
static void all_then_end(struct gen_info *gen)
{
	for(; gen->s_iter != NULL; gen->s_iter = g_slist_next(gen->s_iter))
	{
		if((gen->flags & CGF_NODIRS) && S_ISDIR(DP_SEL_LSTAT(gen->s_iter).st_mode))
			continue;
		if((gen->flags & CGF_LINKSONLY) && !(S_ISLNK(DP_SEL_LSTAT(gen->s_iter).st_mode)))
			continue;
		gen->need_update = TRUE;
		if(!gen->af(gen->min, gen->src, gen->dst, DP_SEL_ROW(gen->s_iter), gen->user))
			break;
	}
	end_command(gen);
}

/* 1998-05-29 -	Callback for when the user clicks on one of the bottom four control
**		buttons. This callback is attached to all buttons, so we need to determine
**		which button was actually clicked.
*/
static gint evt_clicked(GtkWidget *wid, gpointer user)
{
	struct gen_info	*gen = (struct gen_info *) user;

	if(wid == gen->ok)
	{
		gen->need_update = TRUE;
		if(!gen->af(gen->min, gen->src, gen->dst, DP_SEL_ROW(gen->s_iter), gen->user))
			end_command(gen);
		else
			next_or_end(gen);
	}
	else if(wid == gen->all)
		all_then_end(gen);
	else if(wid == gen->skip)
		next_or_end(gen);
	else if(wid == gen->cancel)
		end_command(gen);
	return TRUE;
}

static gint evt_delete(GtkWidget *wid, gpointer user)
{
	struct gen_info	*gen = (struct gen_info *) user;

	end_command(gen);
	return TRUE;
}

/* 1998-07-11 -	Parse key press in general command dialog. Allows use of Enter key to
**		exit the dialog; if "All", "Skip" or "Cancel" is focused, the appropritate
**		action is taken, otherwise the "Ok" action is taken.
*/
static gint evt_key_press(GtkWidget *wid, GdkEventKey *event, gpointer data)
{
	struct gen_info	*gen = (struct gen_info *) data;

	switch(event->keyval)
	{
		case GDK_Return:
			if(!(gen->flags & CGF_NORETURN))
			{
				if(!(gen->flags & CGF_NOALL) && GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(gen->all)))
					gtk_signal_emit_by_name(GTK_OBJECT(gen->all), "clicked", data);
				else if(GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(gen->skip)))
					gtk_signal_emit_by_name(GTK_OBJECT(gen->skip), "clicked", data);
				else if(GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(gen->cancel)))
					gtk_signal_emit_by_name(GTK_OBJECT(gen->cancel), "clicked", data);
				else
					gtk_signal_emit_by_name(GTK_OBJECT(gen->ok), "clicked", data);
			}
			return TRUE;
		case GDK_Escape:
			if(!(gen->flags & CGF_NOESC))
				gtk_signal_emit_by_name(GTK_OBJECT(gen->cancel), "clicked", data);
			return TRUE;
	}
	return FALSE;
}

/* 1998-05-29 -	General purpose command execution framework entry point.
** 1998-05-31 -	Set the CAN_DEFAULT flag on all four buttons, making them a lot
**		lower (of course). This made it look somewhat better.
*/
int cmd_generic(MainInfo *min, guint32 flags, GenBodyFunc bf, GenActionFunc af, GenFreeFunc ff, gpointer user)
{
	static struct gen_info	gen;
	GtkWidget		*vbox;

	gen.flags = flags;
	gen.min	  = min;
	gen.src	  = min->gui->cur_dir;
	gen.dst	  = dp_mirror(min, gen.src);

	if(!fut_cd(gen.src->dir.path, gen.old_path, sizeof gen.old_path))
		return 0;

	if((gen.s_iter = gen.s_slist = dp_get_selection(gen.src)) == NULL)	/* No selection? */
		return 0;

	gen.bf	= bf;
	gen.af	= af;
	gen.ff	= ff;

	gen.dlg = gtk_dialog_new();
	gtk_signal_connect(GTK_OBJECT(gen.dlg), "destroy", GTK_SIGNAL_FUNC(evt_delete), &gen);
	gtk_signal_connect(GTK_OBJECT(&GTK_DIALOG(gen.dlg)->window), "key_press_event", GTK_SIGNAL_FUNC(evt_key_press), &gen);

	if(*((GtkWidget **) user) != NULL)	/* Does user have any widgets to add? */
	{
		vbox = gtk_vbox_new(FALSE, 0);
		gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
		gtk_box_pack_start(GTK_BOX(vbox), *((GtkWidget **) user), TRUE, TRUE, 0);
		gtk_widget_show(*((GtkWidget **) user));
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(gen.dlg)->vbox), vbox, TRUE, TRUE, 0);
		gtk_widget_show(vbox);
	}

	gen.ok = gtk_button_new_with_label("OK");
	if(!(gen.flags & CGF_NOALL))
		gen.all = gtk_button_new_with_label("All");
	else
		gen.all = NULL;
	gen.skip   = gtk_button_new_with_label("Skip");
	gen.cancel = gtk_button_new_with_label("Cancel");
	GTK_WIDGET_SET_FLAGS(gen.ok, GTK_CAN_DEFAULT);

	if(!(gen.flags & CGF_NOALL))
	{
		GTK_WIDGET_SET_FLAGS(gen.all, GTK_CAN_DEFAULT);
		gtk_signal_connect(GTK_OBJECT(gen.all), "clicked", GTK_SIGNAL_FUNC(evt_clicked), &gen);
		gtk_widget_show(gen.all);
	}

	GTK_WIDGET_SET_FLAGS(gen.skip,	GTK_CAN_DEFAULT);
	GTK_WIDGET_SET_FLAGS(gen.cancel, GTK_CAN_DEFAULT);
	gtk_signal_connect(GTK_OBJECT(gen.ok),     "clicked", GTK_SIGNAL_FUNC(evt_clicked), &gen);
	gtk_signal_connect(GTK_OBJECT(gen.skip),   "clicked", GTK_SIGNAL_FUNC(evt_clicked), &gen);
	gtk_signal_connect(GTK_OBJECT(gen.cancel), "clicked", GTK_SIGNAL_FUNC(evt_clicked), &gen);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(gen.dlg)->action_area), gen.ok, TRUE, TRUE, 0);
	if(!(gen.flags & CGF_NOALL))
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(gen.dlg)->action_area), gen.all, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(gen.dlg)->action_area), gen.skip, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(gen.dlg)->action_area), gen.cancel, TRUE, TRUE, 0);
	gtk_widget_show(gen.ok);
	gtk_widget_show(gen.skip);
	gtk_widget_show(gen.cancel);
	gtk_widget_grab_default(gen.ok);
	gen.user = user;
	gen.need_update = FALSE;

	if(first_body(&gen))
	{
		gtk_grab_add(gen.dlg);		/* We're not reentrant. At all. */
		gtk_widget_show(gen.dlg);
		return 1;
	}
	return 0;
}
