/*
** 1999-03-13 -	A (new) module for dealing with user dialogs. The old one was written during
**		my first week of GTK+ programming (back in May 1998, if you really care), and
**		that had started to show a little too much. Hopefully, this is cleaner.
*/

#include <stdio.h>

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include "dialog.h"

struct _Dialog {
	GtkWidget	*dlg;		/* Main GtkDialog object. */
	GtkWidget	*body;		/* Body as provided by user on call. */
	DlgFunc		func;
	gpointer	user;
	guint		last_button;	/* Index of last button created (rightmost). */
	guint		button;		/* Index of button that was clicked. */

	gboolean	keep;		/* When set, dialog is hidden on button click, else destroyed. */
	gboolean	modal;
	guint		*button_ref;	/* Used in modal mode. */
};

#define	DLG_DEFAULT_TITLE	"User Interaction"
#define	DLG_BUTTON_MAX		(32)			/* Max length of a single button label. */

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

static void evt_dialog_destroy(GtkWidget *wid, gpointer user)
{
	Dialog	*dlg = user;

	if(dlg->button_ref != NULL)			/* Store clicked button before destroying dlg. */
		*dlg->button_ref = dlg->button;

	if(dlg->func != NULL)
		dlg->func(dlg, dlg->button, dlg->user);

	if(dlg->modal)
		gtk_main_quit();

	g_free(dlg);
}

/* 1999-03-13 -	Do a "logical" close of the given dialog. Unless user has called dlg_dialog_keep(TRUE)
**		on it, this will really destroy the root dialog widget. Otherwise, it will be hidden.
*/
static void close(Dialog *dlg)
{
	if(dlg != NULL)
	{
		if(dlg->button_ref != NULL)
			*dlg->button_ref = dlg->button;
		gtk_widget_hide(dlg->dlg);
		if(dlg->keep)
		{
			if(dlg->modal)
			{
				dlg->modal = FALSE;
				gtk_main_quit();
			}
		}
		else
			gtk_widget_destroy(dlg->dlg);
	}
}

static gint evt_button_clicked(GtkWidget *wid, gpointer user)
{
	Dialog	*dlg = user;

	dlg->button = GPOINTER_TO_UINT(gtk_object_get_user_data(GTK_OBJECT(wid)));

	close((Dialog *) user);

	return TRUE;
}

static gint evt_dialog_keypress(GtkWidget *wid, GdkEventKey *evt, gpointer user)
{
	Dialog	*dlg = user;

	switch(evt->keyval)
	{
		case GDK_Return:
			dlg->button = 0;
			close(dlg);
			break;
		case GDK_Escape:
			dlg->button = dlg->last_button;
			close(dlg);
			break;
	}
	return TRUE;
}

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

static void build_buttons(Dialog *dlg, const gchar *buttons)
{
	gchar		*base, *ptr, label[DLG_BUTTON_MAX];
	guint		index;
	GtkWidget	*btn;

	for(base = (gchar *) buttons, index = 0; *base; index++)
	{
		for(ptr = label; (ptr - label) < (sizeof label - 1) && *base && *base != '|';)
			*ptr++ = *base++;
		*ptr = '\0';
		if(*base == '|')
			base++;
		btn = gtk_button_new_with_label(label);
		GTK_WIDGET_SET_FLAGS(btn, GTK_CAN_DEFAULT);
		gtk_signal_connect(GTK_OBJECT(btn), "clicked", GTK_SIGNAL_FUNC(evt_button_clicked), dlg);
		gtk_object_set_user_data(GTK_OBJECT(btn), GUINT_TO_POINTER(index));
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg->dlg)->action_area), btn, TRUE, TRUE, 0);
		gtk_widget_show(btn);
		if(index == 0)
			gtk_widget_grab_default(btn);
	}
	dlg->button = dlg->last_button = index - 1;
}

/* 1999-03-14 -	Moved out the body of the dlg_dialog() function to ease the creation of dlg_dialog_hidden(). */
static Dialog * build_dialog(GtkWidget *body, const gchar *title, const gchar *buttons, DlgFunc func, gpointer user)
{
	Dialog	*dlg;

	if(title == NULL)
		title = DLG_DEFAULT_TITLE;
	if(buttons == NULL)
		buttons = "OK";

	dlg = g_malloc(sizeof *dlg);

	dlg->dlg = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(dlg->dlg), title);
	gtk_window_set_position(GTK_WINDOW(dlg->dlg), GTK_WIN_POS_MOUSE);
	gtk_signal_connect(GTK_OBJECT(dlg->dlg), "key_press_event", GTK_SIGNAL_FUNC(evt_dialog_keypress), dlg);
	gtk_signal_connect(GTK_OBJECT(dlg->dlg), "destroy", GTK_SIGNAL_FUNC(evt_dialog_destroy), dlg);

	if(body != NULL)
	{
		GtkWidget	*vbox;

		dlg->body = body;
		vbox = gtk_vbox_new(FALSE, 0);
		gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
		gtk_box_pack_start(GTK_BOX(vbox), dlg->body, TRUE, TRUE, 0);
		gtk_widget_show(dlg->body);
		gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg->dlg)->vbox), vbox, TRUE, TRUE, 0);
		gtk_widget_show(vbox);
	}
	else
		dlg->body = NULL;

	build_buttons(dlg, buttons);
	gtk_grab_add(dlg->dlg);		/* For proper operation when invoked _from_ modal window (e.g. config). */

	dlg->func  = func;
	dlg->user  = user;
	dlg->keep  = FALSE;
	dlg->modal = FALSE;
	dlg->button_ref = NULL;

	return dlg;
}

/* 1999-03-13 -	Create and display a dialog. The top (body) part of the dialog window will show the
**		<body> widgetry. The window title will be set to <title>. The string <buttons> should
**		list the desired button texts, separated by vertical bars (e.g. "OK|Retry|Cancel").
**		When the user clicks a button, <func> is called and passed the dialog itself, the index
**		of the button clicked (0-based) and the <user> pointer. This dialog is non-modal.
*/
Dialog * dlg_dialog(GtkWidget *body, const gchar *title, const gchar *buttons, DlgFunc func, gpointer user)
{
	Dialog	*dlg;

	dlg = build_dialog(body, title, buttons, func, user);
	gtk_widget_show(dlg->dlg);

	return dlg;
}

/* 1999-03-14 -	Just like the main dialog, only this one is not shown. Only useful for dialogs that will
**		be dlg_dialog_wait()'ed for, since otherwise it will never be seen...
*/
Dialog * dlg_dialog_hidden(GtkWidget *body, const gchar *title, const gchar *buttons, DlgFunc func, gpointer user)
{
	return build_dialog(body, title, buttons, func, user);
}

/* 1999-03-13 -	Just a convenience function to create a dialog with a simple text body. */
Dialog * dlg_dialog_simple(const gchar *body, const gchar *title, const gchar *buttons, DlgFunc func, gpointer user)
{
	return dlg_dialog(gtk_label_new(body), title, buttons, func, user);
}

/* 1999-03-13 -	Just to save some typing, and get a consistent look. Could be made into a macro... */
Dialog * dlg_dialog_error(const gchar *msg)
{
	return dlg_dialog_simple(msg, "Error", "Continue", NULL, NULL);
}

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

/* 1999-03-13 -	Hide the dialog. Only useful for modal dialogs with keep-flag set. */
void dlg_dialog_hide(Dialog *dlg)
{
	if(dlg != NULL)
		gtk_widget_hide(dlg->dlg);
}

/* 1999-03-13 -	Set the keep-flag in the dialog, which will avoid closing it when clicked. */
void dlg_dialog_set_keep(Dialog *dlg, gboolean keep)
{
	if(dlg != NULL)
		dlg->keep = keep;
}

/* 1999-03-13 -	Set the size of the dialog window. This is typically useful when the body
**		is some long list, which we want to get a little taller.
*/
void dlg_dialog_set_size(Dialog *dlg, gint width, gint height)
{
	if(dlg != NULL)
		gtk_widget_set_usize(dlg->dlg, width, height);
}

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

/* 1999-03-13 -	Close the dialog down as if the leftmost (positive) button was clicked. It is
**		incredibly dumb to call this from the DlgFunc handler, so don't do that.
*/
void dlg_dialog_close_left(Dialog *dlg)
{
	if(dlg != NULL)
	{
		dlg->button = 0;
		gtk_widget_destroy(dlg->dlg);
	}
}

/* 1999-03-13 -	Close down dialog as if the rightmost (negative) button was clicked. Don't
**		call this from the DlgFunc handler.
*/
void dlg_dialog_close_right(Dialog *dlg)
{
	if(dlg != NULL)
	{
		dlg->button = dlg->last_button;
		gtk_widget_destroy(dlg->dlg);
	}
}

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

/* 1999-03-13 -	Wait for the dialog, making it modal. Returns the index of the button that
**		was used to close the dialog. In this mode, the user callback function is
**		never called.
*/
guint dlg_dialog_wait(Dialog *dlg)
{
	guint	button = 0;

	if(dlg != NULL)
	{
		dlg->button_ref = &button;
		dlg->func = NULL;
		dlg->user = NULL;
		dlg->modal = TRUE;
		gtk_widget_show(dlg->dlg);
		if(GTK_WIDGET_HAS_GRAB(dlg->dlg))
			gtk_grab_remove(dlg->dlg);
		gtk_grab_add(dlg->dlg);
		gtk_main();
	}
	return button;
}

/* 1999-04-26 -	Just a simple convenience call. Saves the caller one variable and an if(). */
guint dlg_dialog_simple_wait(const gchar *body, const gchar *title, const gchar *buttons)
{
	Dialog	*dlg;

	if((dlg = dlg_dialog_simple(body, title, buttons, NULL, NULL)) != NULL)
		return dlg_dialog_wait(dlg);
	return 1;	/* Probably less harmful than 0, which would indicate "OK" or some such. */
}

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

/* 1999-03-13 -	Destroy the dialog. The callback will *not* be called!
** 1999-04-02 -	Removed modal check, since, er, it segfaults. Pure magic.
*/
void dlg_dialog_destroy(Dialog *dialog)
{
	if(dialog == NULL)
		return;

	dialog->func = NULL;		/* Disable the callback first. */
	dialog->user = NULL;

	/* BUG BUG BUG: If this check is compiled in, and gentoo is compiled
	** with optimization on using gcc 2.8.1, this function segfaults for
	** me every time, even if the condition is false... Weird.
	*/
	if(dialog->modal)
	{
		dialog->modal = FALSE;
		gtk_main_quit();
	}

	gtk_widget_destroy(dialog->dlg);
}
