/*
 * e-table-column-selector.c
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * This program 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 Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * SECTION: e-table-column-selector
 * @include: e-util/e-util.h
 * @short_description: Select columns for an #ETable or #ETree
 *
 * #ETableColumnSelector is a widget for choosing and ordering the
 * available columns of an #ETable or #ETree.
 **/

#include "e-table-column-selector.h"

#include <config.h>
#include <glib/gi18n-lib.h>

#include "e-util/e-table-specification.h"

#define E_TABLE_COLUMN_SELECTOR_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_TABLE_COLUMN_SELECTOR, ETableColumnSelectorPrivate))

struct _ETableColumnSelectorPrivate {
	ETableState *state;
};

enum {
	PROP_0,
	PROP_STATE
};

enum {
	COLUMN_ACTIVE,
	COLUMN_TITLE,
	COLUMN_SPECIFICATION,
	COLUMN_EXPANSION,
	NUM_COLUMNS
};

G_DEFINE_TYPE (
	ETableColumnSelector,
	e_table_column_selector,
	E_TYPE_TREE_VIEW_FRAME)

static void
table_column_selector_toggled_cb (GtkCellRendererToggle *renderer,
                                  const gchar *path_string,
                                  GtkTreeView *tree_view)
{
	GtkTreeModel *tree_model;
	GtkTreeIter iter;
	gboolean active;

	tree_model = gtk_tree_view_get_model (tree_view);
	gtk_tree_model_get_iter_from_string (tree_model, &iter, path_string);

	gtk_tree_model_get (tree_model, &iter, COLUMN_ACTIVE, &active, -1);

	gtk_list_store_set (
		GTK_LIST_STORE (tree_model),
		&iter, COLUMN_ACTIVE, !active, -1);
}

static GtkTreeModel *
table_column_selector_build_model (ETableColumnSelector *selector)
{
	GtkListStore *list_store;
	GtkTreeIter iter;
	ETableState *state;
	ETableSpecification *specification;
	GPtrArray *columns;
	GHashTable *columns_added;
	guint ii;

	state = e_table_column_selector_get_state (selector);
	specification = e_table_state_ref_specification (state);
	columns = e_table_specification_ref_columns (specification);

	/* Set of ETableColumnSpecifications to help keep track
	 * of which ones are already added to the list store. */
	columns_added = g_hash_table_new (NULL, NULL);

	list_store = gtk_list_store_new (
		NUM_COLUMNS,
		G_TYPE_BOOLEAN,
		G_TYPE_STRING,
		E_TYPE_TABLE_COLUMN_SPECIFICATION,
		G_TYPE_DOUBLE);

	/* Add selected columns from ETableState first. */

	for (ii = 0; ii < state->col_count; ii++) {
		ETableColumnSpecification *column_spec;
		gdouble expansion;

		column_spec = state->column_specs[ii];
		expansion = state->expansions[ii];

		gtk_list_store_append (list_store, &iter);

		gtk_list_store_set (
			list_store, &iter,
			COLUMN_ACTIVE, TRUE,
			COLUMN_TITLE, column_spec->title,
			COLUMN_SPECIFICATION, column_spec,
			COLUMN_EXPANSION, expansion,
			-1);

		g_hash_table_add (columns_added, column_spec);
	}

	/* Add the rest of the columns from ETableSpecification. */

	for (ii = 0; ii < columns->len; ii++) {
		ETableColumnSpecification *column_spec;

		column_spec = g_ptr_array_index (columns, ii);

		if (g_hash_table_contains (columns_added, column_spec))
			continue;

		/* XXX We have this unfortunate "disabled" flag because
		 *     past developers made the mistake of having table
		 *     config files reference columns by number instead
		 *     of name so removing a column would break all the
		 *     table config files out in the wild. */
		if (column_spec->disabled)
			continue;

		gtk_list_store_append (list_store, &iter);

		gtk_list_store_set (
			list_store, &iter,
			COLUMN_ACTIVE, FALSE,
			COLUMN_TITLE, column_spec->title,
			COLUMN_SPECIFICATION, column_spec,
			COLUMN_EXPANSION, 1.0,
			-1);

		g_hash_table_add (columns_added, column_spec);
	}

	g_hash_table_destroy (columns_added);

	g_object_unref (specification);
	g_ptr_array_unref (columns);

	return GTK_TREE_MODEL (list_store);
}

static void
table_column_selector_set_state (ETableColumnSelector *selector,
                                 ETableState *state)
{
	g_return_if_fail (E_IS_TABLE_STATE (state));
	g_return_if_fail (selector->priv->state == NULL);

	selector->priv->state = g_object_ref (state);
}

static void
table_column_selector_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_STATE:
			table_column_selector_set_state (
				E_TABLE_COLUMN_SELECTOR (object),
				g_value_get_object (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
table_column_selector_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_STATE:
			g_value_set_object (
				value,
				e_table_column_selector_get_state (
				E_TABLE_COLUMN_SELECTOR (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
table_column_selector_dispose (GObject *object)
{
	ETableColumnSelectorPrivate *priv;

	priv = E_TABLE_COLUMN_SELECTOR_GET_PRIVATE (object);

	g_clear_object (&priv->state);

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_table_column_selector_parent_class)->
		dispose (object);
}

static void
table_column_selector_constructed (GObject *object)
{
	ETableColumnSelector *selector;
	ETreeViewFrame *tree_view_frame;
	GtkAction *action;
	GtkTreeView *tree_view;
	GtkTreeModel *tree_model;
	GtkTreeSelection *selection;
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;
	const gchar *tooltip;

	selector = E_TABLE_COLUMN_SELECTOR (object);

	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (e_table_column_selector_parent_class)->constructed (object);

	tree_view_frame = E_TREE_VIEW_FRAME (object);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);

	gtk_tree_view_set_reorderable (tree_view, TRUE);
	gtk_tree_view_set_headers_visible (tree_view, FALSE);

	selection = gtk_tree_view_get_selection (tree_view);
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	/* Configure the toolbar actions. */

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_ADD);
	gtk_action_set_visible (action, FALSE);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
	gtk_action_set_visible (action, FALSE);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_TOP);
	tooltip = _("Move selected column names to top");
	gtk_action_set_tooltip (action, tooltip);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_UP);
	tooltip = _("Move selected column names up one row");
	gtk_action_set_tooltip (action, tooltip);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN);
	tooltip = _("Move selected column names down one row");
	gtk_action_set_tooltip (action, tooltip);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM);
	tooltip = _("Move selected column names to bottom");
	gtk_action_set_tooltip (action, tooltip);

	action = e_tree_view_frame_lookup_toolbar_action (
		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_SELECT_ALL);
	tooltip = _("Select all column names");
	gtk_action_set_tooltip (action, tooltip);

	/* Configure the tree view columns. */

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_toggle_new ();
	gtk_tree_view_column_pack_start (column, renderer, FALSE);
	gtk_tree_view_column_add_attribute (
		column, renderer, "active", COLUMN_ACTIVE);
	gtk_tree_view_append_column (tree_view, column);

	g_signal_connect (
		renderer, "toggled",
		G_CALLBACK (table_column_selector_toggled_cb),
		tree_view);

	column = gtk_tree_view_column_new ();
	renderer = gtk_cell_renderer_text_new ();
	gtk_tree_view_column_pack_start (column, renderer, FALSE);
	gtk_tree_view_column_add_attribute (
		column, renderer, "text", COLUMN_TITLE);
	gtk_tree_view_append_column (tree_view, column);

	/* Create and populate the tree model. */

	tree_model = table_column_selector_build_model (selector);
	gtk_tree_view_set_model (tree_view, tree_model);
	g_object_unref (tree_model);
}

static void
e_table_column_selector_class_init (ETableColumnSelectorClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (ETableColumnSelectorPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->set_property = table_column_selector_set_property;
	object_class->get_property = table_column_selector_get_property;
	object_class->dispose = table_column_selector_dispose;
	object_class->constructed = table_column_selector_constructed;

	g_object_class_install_property (
		object_class,
		PROP_STATE,
		g_param_spec_object (
			"state",
			"Table State",
			"Column state of the source table",
			E_TYPE_TABLE_STATE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT_ONLY |
			G_PARAM_STATIC_STRINGS));
}

static void
e_table_column_selector_init (ETableColumnSelector *selector)
{
	selector->priv = E_TABLE_COLUMN_SELECTOR_GET_PRIVATE (selector);
}

/**
 * e_table_column_selector_new:
 * @state: an #ETableState
 *
 * Creates a new #ETableColumnSelector, obtaining the initial column
 * selection content from @state.
 *
 * Note that @state remains unmodified until e_table_column_selector_apply()
 * is called.
 *
 * Returns: an #ETableColumnSelector
 **/
GtkWidget *
e_table_column_selector_new (ETableState *state)
{
	g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);

	return g_object_new (
		E_TYPE_TABLE_COLUMN_SELECTOR,
		"state", state, NULL);
}

/**
 * e_table_column_selector_get_state:
 * @selector: an #ETableColumnSelector
 *
 * Returns the #ETableState passed to e_table_column_selector_new().
 *
 * Returns: an #ETableState
 **/
ETableState *
e_table_column_selector_get_state (ETableColumnSelector *selector)
{
	g_return_val_if_fail (E_IS_TABLE_COLUMN_SELECTOR (selector), NULL);

	return selector->priv->state;
}

/**
 * e_table_column_selector_apply:
 * @selector: an #ETableColumnSelector
 *
 * Applies the user's column preferences to the @selector's
 * #ETableColumnSelector:state instance.
 **/
void
e_table_column_selector_apply (ETableColumnSelector *selector)
{
	ETableState *state;
	ETreeViewFrame *tree_view_frame;
	GtkTreeView *tree_view;
	GtkTreeModel *tree_model;
	GArray *active_iters;
	GtkTreeIter iter;
	gboolean iter_valid;
	guint ii;

	g_return_if_fail (E_IS_TABLE_COLUMN_SELECTOR (selector));

	tree_view_frame = E_TREE_VIEW_FRAME (selector);
	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
	tree_model = gtk_tree_view_get_model (tree_view);

	/* Collect all the "active" rows into an array of iterators. */

	active_iters = g_array_new (FALSE, TRUE, sizeof (GtkTreeIter));

	iter_valid = gtk_tree_model_get_iter_first (tree_model, &iter);

	while (iter_valid) {
		gboolean active;

		gtk_tree_model_get (
			tree_model, &iter, COLUMN_ACTIVE, &active, -1);

		if (active)
			g_array_append_val (active_iters, iter);

		iter_valid = gtk_tree_model_iter_next (tree_model, &iter);
	}

	/* Reconstruct the ETableState from the array of iterators. */

	state = e_table_column_selector_get_state (selector);

	for (ii = 0; ii < state->col_count; ii++)
		g_object_unref (state->column_specs[ii]);
	g_free (state->column_specs);
	g_free (state->expansions);

	state->col_count = active_iters->len;
	state->column_specs = g_new0 (
		ETableColumnSpecification *, active_iters->len);
	state->expansions = g_new0 (gdouble, active_iters->len);

	for (ii = 0; ii < active_iters->len; ii++) {
		ETableColumnSpecification *column_spec;
		gdouble expansion;

		iter = g_array_index (active_iters, GtkTreeIter, ii);

		gtk_tree_model_get (
			tree_model, &iter,
			COLUMN_SPECIFICATION, &column_spec,
			COLUMN_EXPANSION, &expansion,
			-1);

		state->column_specs[ii] = g_object_ref (column_spec);
		state->expansions[ii] = expansion;

		g_object_unref (column_spec);
	}

	g_array_free (active_iters, TRUE);
}
