/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * E-table-config.c: The ETable config dialog.
 *
 * Authors:
 *   Chris Lahey <clahey@ximian.com>
 *   Miguel de Icaza (miguel@ximian.com)
 *
 * FIXME:
 *    Sort Dialog: when text is selected, the toggle button switches state.
 *    Make Clear all work.
 *
 * (C) 2000, 2001 Ximian, Inc.
 */

#include <config.h>

#include "e-table-config.h"

#include <stdlib.h>
#include <string.h>
#include <gtk/gtkentry.h>
#include <gtk/gtklabel.h>
#include <gtk/gtknotebook.h>
#include <gtk/gtksignal.h>
#include <gtk/gtktogglebutton.h>
#include <libgnomeui/gnome-dialog.h>
#include <libgnomeui/gnome-propertybox.h>
#include <glade/glade.h>
#include "gal/util/e-util.h"
#include "gal/util/e-i18n.h"


#define PARENT_TYPE (gtk_object_get_type())

static GtkObjectClass *config_parent_class;

enum {
	CHANGED,
	LAST_SIGNAL
};

enum {
	ARG_0,
	ARG_STATE,
};

static guint e_table_config_signals [LAST_SIGNAL] = { 0, };

static void
config_destroy (GtkObject *object)
{
	ETableConfig *config = E_TABLE_CONFIG (object);

	gtk_object_destroy (GTK_OBJECT (config->state));
	gtk_object_unref (GTK_OBJECT (config->source_state));
	gtk_object_unref (GTK_OBJECT (config->source_spec));

	g_slist_free (config->column_names);
	config->column_names = NULL;
	
	GTK_OBJECT_CLASS (config_parent_class)->destroy (object);
}

static void
config_get_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
	ETableConfig *config = E_TABLE_CONFIG (o);

	switch (arg_id){
	case ARG_STATE:
		GTK_VALUE_OBJECT (*arg) = (GtkObject *) config->state;
		break;

	default:
		break;
	}
}

static void
e_table_config_changed (ETableConfig *config, ETableState *state)
{
	g_return_if_fail (config != NULL);
	g_return_if_fail (E_IS_TABLE_CONFIG (config));

	
	gtk_signal_emit(GTK_OBJECT(config),
			e_table_config_signals [CHANGED],
			state);
}

static void
config_class_init (GtkObjectClass *object_class)
{
	ETableConfigClass *klass = E_TABLE_CONFIG_CLASS(object_class);

	config_parent_class   = gtk_type_class (PARENT_TYPE);
	
	klass->changed        = NULL;

	object_class->get_arg = config_get_arg;
	object_class->destroy = config_destroy;

	e_table_config_signals [CHANGED] =
		gtk_signal_new ("changed",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableConfigClass, changed),
				gtk_marshal_NONE__NONE,
				GTK_TYPE_NONE, 0);

	gtk_object_class_add_signals (object_class, e_table_config_signals, LAST_SIGNAL);

	gtk_object_add_arg_type ("ETableConfig::state", E_TABLE_STATE_TYPE,
				 GTK_ARG_READABLE, ARG_STATE);
}

static ETableColumnSpecification *
find_column_in_spec (ETableSpecification *spec, int model_col)
{
	ETableColumnSpecification **column;

	for (column = spec->columns; *column; column++){
		if ((*column)->model_col != model_col)
			continue;

		return *column;
	}

	return NULL;
}

static int
find_model_column_by_name (ETableSpecification *spec, const char *s)
{
	ETableColumnSpecification **column;

	for (column = spec->columns; *column; column++){

		if (g_strcasecmp ((*column)->title, s) == 0)
			return (*column)->model_col;
	}
	return -1;
}

static void
update_sort_and_group_config_dialog (ETableConfig *config, gboolean is_sort)
{
	ETableConfigSortWidgets *widgets;
	int count, i;

	if (is_sort){
		count = e_table_sort_info_sorting_get_count (
			config->temp_state->sort_info);
		widgets = &config->sort [0];
	} else {
		count = e_table_sort_info_grouping_get_count (
			config->temp_state->sort_info);
		widgets = &config->group [0];
	}
	
	for (i = 0; i < 4; i++){
		gboolean sensitive = (i <= count);
		char *text = "";
		
		gtk_widget_set_sensitive (widgets [i].frames, sensitive);

		/*
		 * Sorting is set, auto select the text
		 */
		gtk_signal_handler_block (
			GTK_OBJECT (widgets [i].radio_ascending),
			widgets [i].toggled_id);
		gtk_signal_handler_block (
			GTK_OBJECT (widgets [i].combo->entry),
			widgets [i].changed_id);
		
		if (i < count){
			GtkToggleButton *a, *d;
			ETableSortColumn col =
				is_sort
				? e_table_sort_info_sorting_get_nth (
					config->temp_state->sort_info,
					i)
				:  e_table_sort_info_grouping_get_nth (
					config->temp_state->sort_info,
					i);
			
			ETableColumnSpecification *column =
				find_column_in_spec (config->source_spec, col.column);

			if (!column){
				/*
				 * This is a bug in the programmer
				 * stuff, but by the time we arrive
				 * here, the user has been given a
				 * warning
				 */
				continue;
			}

			text = gettext (column->title);

			/*
			 * Update radio buttons
			 */
			a = GTK_TOGGLE_BUTTON (
				widgets [i].radio_ascending);
			d = GTK_TOGGLE_BUTTON (
				widgets [i].radio_descending);
			
			gtk_toggle_button_set_active (col.ascending ? a:d, 1);
		} else {
			GtkToggleButton *t;
			
			t = GTK_TOGGLE_BUTTON (
				widgets [i].radio_ascending);

			if (is_sort)
				g_assert (widgets [i].radio_ascending != config->group [i].radio_ascending);
			else
				g_assert (widgets [i].radio_ascending != config->sort [i].radio_ascending);
			gtk_toggle_button_set_active (t, 1);
		}

		/* Set the text */
		gtk_combo_text_set_text (widgets [i].combo, text);

		gtk_signal_handler_unblock (
			GTK_OBJECT (widgets [i].radio_ascending),
			widgets [i].toggled_id);
		gtk_signal_handler_unblock (
			GTK_OBJECT (widgets [i].combo->entry),
			widgets [i].changed_id);
	}
}

static void
config_sort_info_update (ETableConfig *config)
{
	ETableSortInfo *info = config->state->sort_info;
	GString *res;
	int count, i;

	count = e_table_sort_info_sorting_get_count (info);
	res = g_string_new ("");

	for (i = 0; i < count; i++) {
		ETableSortColumn col = e_table_sort_info_sorting_get_nth (info, i);
		ETableColumnSpecification *column;
		
		column = find_column_in_spec (config->source_spec, col.column);
		if (!column){
			g_warning ("Could not find column model in specification");
			continue;
		}
		
		g_string_append (res, gettext ((column)->title));
		g_string_append_c (res, ' ');
		g_string_append (
			res,
			col.ascending ?
			_("(Ascending)") : _("(Descending)"));

		if ((i + 1) != count)
			g_string_append (res, ", ");
	}
	
	if (res->str [0] == 0)
		g_string_append (res, _("Not sorted"));
	
	gtk_label_set_text (GTK_LABEL(config->sort_label), res->str);

	g_string_free (res, TRUE);
}

static void
config_group_info_update (ETableConfig *config)
{
	ETableSortInfo *info = config->state->sort_info;
	GString *res;
	int count, i;

	count = e_table_sort_info_grouping_get_count (info);
	res = g_string_new ("");

	for (i = 0; i < count; i++) {
		ETableSortColumn col = e_table_sort_info_grouping_get_nth (info, i);
		ETableColumnSpecification *column;

		column = find_column_in_spec (config->source_spec, col.column);
		if (!column){
			g_warning ("Could not find model column in specification");
			continue;
		}
		
		g_string_append (res, gettext ((column)->title));
		g_string_append_c (res, ' ');
		g_string_append (
			res,
			col.ascending ?
			_("(Ascending)") : _("(Descending)"));

		if ((i+1) != count)
			g_string_append (res, ", ");
	}
	if (res->str [0] == 0)
		g_string_append (res, _("No grouping"));
	
	gtk_label_set_text (GTK_LABEL (config->group_label), res->str);
	g_string_free (res, TRUE);
}

static void
config_fields_info_update (ETableConfig *config)
{
	ETableColumnSpecification **column;
	GString *res = g_string_new ("");
	int i;

	for (i = 0; i < config->state->col_count; i++){
		for (column = config->source_spec->columns; *column; column++){

			if (config->state->columns [i] != (*column)->model_col)
				continue;
			
			g_string_append (res, gettext ((*column)->title));
			if (column [1])
				g_string_append (res, ", ");
		}
	}
	
	gtk_label_set_text (GTK_LABEL (config->fields_label), res->str);
	g_string_free (res, TRUE);
}

static void
do_sort_and_group_config_dialog (ETableConfig *config, gboolean is_sort)
{
	GnomeDialog *dialog;
	int button, running = 1;

	config->temp_state = e_table_state_duplicate (config->state);

	update_sort_and_group_config_dialog (config, is_sort);

	gtk_widget_grab_focus (GTK_WIDGET (
		is_sort
		? config->sort [0].combo
		: config->group [0].combo));
		

	if (is_sort)
		dialog = GNOME_DIALOG (config->dialog_sort);
	else
		dialog = GNOME_DIALOG (config->dialog_group_by);
	
	do {
		button = gnome_dialog_run (dialog);
		switch (button){
		case 0:
			if (is_sort){
				e_table_sort_info_sorting_truncate (
					config->state->sort_info, 0);
			} else {
				e_table_sort_info_grouping_truncate (
					config->state->sort_info, 0);
			}
			update_sort_and_group_config_dialog (config, is_sort);
			continue;

			/* OK */
		case 1:
			gtk_object_unref (GTK_OBJECT (config->state));
			config->state = config->temp_state;
			config->temp_state = 0;
			running = 0;
			gnome_property_box_changed (
				GNOME_PROPERTY_BOX (config->dialog_toplevel));
			break;

			/* CANCEL */
		case 2:
			gtk_object_unref (GTK_OBJECT (config->temp_state));
			config->temp_state = 0;
			running = 0;
			break;
		}
		
	} while (running);
	gnome_dialog_close (GNOME_DIALOG (dialog));

	if (is_sort)
		config_sort_info_update (config);
	else
		config_group_info_update (config);
}

#if 0
static GtkWidget *
e_table_proxy_etable_new (void)
{
	return gtk_label_new ("Waiting for the ETable/ETree\nmerger to be commited");
}
#endif

static void
config_button_fields (GtkWidget *widget, ETableConfig *config)
{
	gnome_dialog_run (GNOME_DIALOG(config->dialog_show_fields));
	gnome_dialog_close (GNOME_DIALOG (config->dialog_show_fields));
}
		
static void
config_button_sort (GtkWidget *widget, ETableConfig *config)
{
	do_sort_and_group_config_dialog (config, TRUE);
}

static void
config_button_group (GtkWidget *widget, ETableConfig *config)
{
	do_sort_and_group_config_dialog (config, FALSE);
}

static void
dialog_destroyed (GtkObject *dialog, ETableConfig *config)
{
	gtk_object_destroy (GTK_OBJECT (config));
}

static void
dialog_apply (GnomePropertyBox *pbox, gint page_num, ETableConfig *config)
{
	if (page_num != -1)
		return;

	e_table_config_changed (config, config->state);
}

/*
 * Invoked by the Glade auto-connect code
 */
GtkWidget *e_table_proxy_gtk_combo_text_new (void);
GtkWidget *
e_table_proxy_gtk_combo_text_new (void)
{
	return gtk_combo_text_new (TRUE);
}

#if 0
static GtkWidget *
configure_dialog (GladeXML *gui, const char *widget_name, ETableConfig *config)
{
	GtkWidget *w;
	
	w = glade_xml_get_widget (gui, widget_name);

	return w;
}
#endif

static void
connect_button (ETableConfig *config, GladeXML *gui, const char *widget_name, void *cback)
{
	GtkWidget *button = glade_xml_get_widget (gui, widget_name);

	if (button) {
		gtk_signal_connect (GTK_OBJECT (button), "clicked",
				    GTK_SIGNAL_FUNC (cback), config);
	}
}

static void
sort_entry_changed (GtkEntry *entry, ETableConfigSortWidgets *sort)
{
	ETableConfig *config = sort->e_table_config;
	ETableSortInfo *sort_info = config->temp_state->sort_info;
	ETableConfigSortWidgets *base = &config->sort[0];
	int idx = sort - base;
	
	char *s = gtk_entry_get_text (entry);

	if (s && s [0] && g_hash_table_lookup (sort->combo->elements, s)){
		ETableSortColumn c;
		int col;
		
		col = find_model_column_by_name (config->source_spec, s);
		if (col == -1){
			g_warning ("sort: This should not happen (%s)", s);
			return;
		}

		c.ascending = GTK_TOGGLE_BUTTON (
			config->sort [idx].radio_ascending)->active;
		c.column = col;
		e_table_sort_info_sorting_set_nth (sort_info, idx, c);
		  
		update_sort_and_group_config_dialog (config, TRUE);
	}  else {
		e_table_sort_info_sorting_truncate (sort_info, idx);
		update_sort_and_group_config_dialog (config, TRUE);
	}
}

static void
sort_ascending_toggled (GtkToggleButton *t, ETableConfigSortWidgets *sort)
{
	ETableConfig *config = sort->e_table_config;
	ETableSortInfo *si = config->temp_state->sort_info;
	ETableConfigSortWidgets *base = &config->sort[0];
	int idx = sort - base;
	ETableSortColumn c;
	
	c = e_table_sort_info_sorting_get_nth (si, idx);
	c.ascending = t->active;
	e_table_sort_info_sorting_set_nth (si, idx, c);
}

static void
configure_sort_dialog (ETableConfig *config, GladeXML *gui)
{
	GSList *l;
	int i;
	
	for (i = 0; i < 4; i++){
		char buffer [80];

		snprintf (buffer, sizeof (buffer), "sort-combo-%d", i + 1);
		config->sort [i].combo = GTK_COMBO_TEXT (
			glade_xml_get_widget (gui, buffer));

		gtk_combo_text_add_item (config->sort [i].combo, "", "");

		snprintf (buffer, sizeof (buffer), "frame-sort-%d", i + 1);
		config->sort [i].frames = 
			glade_xml_get_widget (gui, buffer);

		snprintf (
			buffer, sizeof (buffer),
			"radiobutton-ascending-sort-%d", i+1);
		config->sort [i].radio_ascending = glade_xml_get_widget (
			gui, buffer);

		snprintf (
			buffer, sizeof (buffer),
			"radiobutton-descending-sort-%d", i+1);
		config->sort [i].radio_descending = glade_xml_get_widget (
			gui, buffer);

		config->sort [i].e_table_config = config;
	}

	for (l = config->column_names; l; l = l->next){
		char *label = l->data;

		for (i = 0; i < 4; i++){
			gtk_combo_text_add_item (
				config->sort [i].combo,
				gettext (label), label);
		}
	}

	/*
	 * After we have runtime modified things, signal connect
	 */
	for (i = 0; i < 4; i++){
		config->sort [i].changed_id = gtk_signal_connect (
			GTK_OBJECT (config->sort [i].combo->entry),
			"changed", sort_entry_changed, &config->sort [i]);

		config->sort [i].toggled_id = gtk_signal_connect (
			GTK_OBJECT (config->sort [i].radio_ascending),
			"toggled", sort_ascending_toggled, &config->sort [i]);
	}
}

static void
group_entry_changed (GtkEntry *entry, ETableConfigSortWidgets *group)
{
	ETableConfig *config = group->e_table_config;
	ETableSortInfo *sort_info = config->temp_state->sort_info;
	ETableConfigSortWidgets *base = &config->group[0];
	int idx = group - base;
	char *s = gtk_entry_get_text (entry);

	if (s && s [0] && g_hash_table_lookup (group->combo->elements, s)){
		ETableSortColumn c;
		int col;
		
		col = find_model_column_by_name (config->source_spec, s);
		if (col == -1){
			g_warning ("grouping: this should not happen, %s", s);
			return;
		}

		c.ascending = GTK_TOGGLE_BUTTON (
			config->group [idx].radio_ascending)->active;
		c.column = col;
		e_table_sort_info_grouping_set_nth (sort_info, idx, c);
		  
		update_sort_and_group_config_dialog (config, FALSE);
	}  else {
		e_table_sort_info_grouping_truncate (sort_info, idx);
		update_sort_and_group_config_dialog (config, FALSE);
	}
}

static void
group_ascending_toggled (GtkToggleButton *t, ETableConfigSortWidgets *group)
{
	ETableConfig *config = group->e_table_config;
	ETableSortInfo *si = config->temp_state->sort_info;
	ETableConfigSortWidgets *base = &config->group[0];
	int idx = group - base;
	ETableSortColumn c;
	
	c = e_table_sort_info_grouping_get_nth (si, idx);
	c.ascending = t->active;
	e_table_sort_info_grouping_set_nth (si, idx, c);
}

static void
configure_group_dialog (ETableConfig *config, GladeXML *gui)
{
	GSList *l;
	int i;
	
	for (i = 0; i < 4; i++){
		char buffer [80];

		snprintf (buffer, sizeof (buffer), "group-combo-%d", i + 1);
		config->group [i].combo = GTK_COMBO_TEXT (
			glade_xml_get_widget (gui, buffer));

		gtk_combo_text_add_item (config->group [i].combo, "", "");

		snprintf (buffer, sizeof (buffer), "frame-group-%d", i + 1);
		config->group [i].frames = 
			glade_xml_get_widget (gui, buffer);

		snprintf (
			buffer, sizeof (buffer),
			"radiobutton-ascending-group-%d", i+1);
		config->group [i].radio_ascending = glade_xml_get_widget (
			gui, buffer);

		snprintf (
			buffer, sizeof (buffer),
			"radiobutton-descending-group-%d", i+1);
		config->group [i].radio_descending = glade_xml_get_widget (
			gui, buffer);

		snprintf (
			buffer, sizeof (buffer),
			"checkbutton-group-%d", i+1);
		config->group [i].view_check = glade_xml_get_widget (
			gui, buffer);
		
		config->group [i].e_table_config = config;
	}

	
	for (l = config->column_names; l; l = l->next){
		char *label = l->data;

		for (i = 0; i < 4; i++){
			gtk_combo_text_add_item (
				config->group [i].combo,
				gettext (label), label);
		}
	}

	/*
	 * After we have runtime modified things, signal connect
	 */
	for (i = 0; i < 4; i++){
		config->group [i].changed_id = gtk_signal_connect (
			GTK_OBJECT (config->group [i].combo->entry),
			"changed", group_entry_changed, &config->group [i]);

		config->group [i].toggled_id = gtk_signal_connect (
			GTK_OBJECT (config->group [i].radio_ascending),
			"toggled", group_ascending_toggled, &config->group [i]);
	}
}

static void
setup_gui (ETableConfig *config)
{
	GladeXML *gui;

	if (e_table_sort_info_get_can_group (config->state->sort_info)) {
		gui = glade_xml_new_with_domain (ETABLE_GLADEDIR "/e-table-config.glade", NULL, PACKAGE);
	} else {
		gui = glade_xml_new_with_domain (ETABLE_GLADEDIR "/e-table-config-no-group.glade", NULL, PACKAGE);
	}
	
	config->dialog_toplevel = glade_xml_get_widget (
		gui, "e-table-config");

	gtk_notebook_set_show_tabs (
		GTK_NOTEBOOK (GNOME_PROPERTY_BOX (
			config->dialog_toplevel)->notebook),
		FALSE);
	
	config->dialog_show_fields = glade_xml_get_widget (
		gui, "dialog-show-fields");
	config->dialog_group_by =  glade_xml_get_widget (
		gui, "dialog-group-by");
	config->dialog_sort = glade_xml_get_widget (
		gui, "dialog-sort");

	config->sort_label = glade_xml_get_widget (
		gui, "label-sort");
	config->group_label = glade_xml_get_widget (
		gui, "label-group");
	config->fields_label = glade_xml_get_widget (
		gui, "label-fields");

	connect_button (config, gui, "button-sort", config_button_sort);
	connect_button (config, gui, "button-group", config_button_group);
	connect_button (config, gui, "button-fields", config_button_fields);
	
	configure_sort_dialog (config, gui);
	configure_group_dialog (config, gui);
	
	gtk_signal_connect (
		GTK_OBJECT (config->dialog_toplevel), "destroy",
		GTK_SIGNAL_FUNC (dialog_destroyed), config);

	gtk_signal_connect (
		GTK_OBJECT (config->dialog_toplevel), "apply",
		GTK_SIGNAL_FUNC (dialog_apply), config);

	gtk_object_unref (GTK_OBJECT (gui));
}

static void
config_init (ETableConfig *config)
{
	glade_gnome_init ();
}

ETableConfig *
e_table_config_construct (ETableConfig        *config,
			  const char          *header,
			  ETableSpecification *spec,
			  ETableState         *state)
{
	ETableColumnSpecification **column;

	g_return_val_if_fail (config != NULL, NULL);
	g_return_val_if_fail (header != NULL, NULL);
	g_return_val_if_fail (spec != NULL, NULL);
	g_return_val_if_fail (state != NULL, NULL);
	
	config->source_spec = spec;
	config->source_state = state;

	gtk_object_ref (GTK_OBJECT (config->source_spec));
	gtk_object_ref (GTK_OBJECT (config->source_state));

	config->state = e_table_state_duplicate (state);

	for (column = config->source_spec->columns; *column; column++){
		char *label = (*column)->title;

		config->column_names = g_slist_append (
			config->column_names, label);
	}

	setup_gui (config);

	config_sort_info_update   (config);
	config_group_info_update  (config);
	config_fields_info_update (config);
	
	return E_TABLE_CONFIG (config);
}

/**
 * e_table_config_new:
 * @header: The title of the dialog for the ETableConfig.
 * @spec: The specification for the columns to allow.
 * @state: The current state of the configuration.
 *
 * Creates a new ETable config object.
 *
 * Returns: The config object.
 */
ETableConfig *
e_table_config_new (const char          *header,
		    ETableSpecification *spec,
		    ETableState         *state)
{
	ETableConfig *config = gtk_type_new (E_TABLE_CONFIG_TYPE);

	if (e_table_config_construct (config, header, spec, state) == NULL){
		gtk_object_destroy (GTK_OBJECT (config));
		return NULL;
	}

	gtk_widget_show (config->dialog_toplevel);
	return E_TABLE_CONFIG (config);
}

/**
 * e_table_config_raise:
 * @config: The ETableConfig object.
 *
 * Raises the dialog associated with this ETableConfig object.
 */
void
e_table_config_raise (ETableConfig *config)
{
	gdk_window_raise (GTK_WIDGET (config->dialog_toplevel)->window);
}

E_MAKE_TYPE(e_table_config, "ETableConfig", ETableConfig, config_class_init, config_init, PARENT_TYPE);