/*
 * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
 * Copyright (C) 2010 David King <davidk@openismus.com>
 * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
 *
 * This library 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; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include <string.h>
#include <glib/gi18n-lib.h>
#include <libgda/libgda.h>
#include "gdaui-data-proxy.h"
#include "gdaui-data-selector.h"
#include "gdaui-raw-grid.h"
#include "gdaui-data-proxy-info.h"
#include "gdaui-enum-types.h"

static void gdaui_data_proxy_info_class_init (GdauiDataProxyInfoClass * class);
static void gdaui_data_proxy_info_init (GdauiDataProxyInfo *wid);
static void gdaui_data_proxy_info_dispose (GObject *object);

static void gdaui_data_proxy_info_set_property (GObject *object,
						guint param_id,
						const GValue *value,
						GParamSpec *pspec);
static void gdaui_data_proxy_info_get_property (GObject *object,
						guint param_id,
						GValue *value,
						GParamSpec *pspec);

static void modif_buttons_make (GdauiDataProxyInfo *info);
static void modif_buttons_update (GdauiDataProxyInfo *info);

static void data_proxy_proxy_changed_cb (GdauiDataProxy *data_proxy, GdaDataProxy *proxy, GdauiDataProxyInfo *info);
static void proxy_changed_cb (GdaDataProxy *proxy, GdauiDataProxyInfo *info);
static void proxy_sample_changed_cb (GdaDataProxy *proxy, gint sample_start, gint sample_end, GdauiDataProxyInfo *info);
static void proxy_row_changed_cb (GdaDataProxy *proxy, gint row, GdauiDataProxyInfo *info);
static void proxy_reset_cb (GdaDataProxy *wid, GdauiDataProxyInfo *info);

static void raw_grid_selection_changed_cb (GdauiRawGrid *grid, GdauiDataProxyInfo *info);


struct _GdauiDataProxyInfoPriv
{
	GdauiDataProxy *data_proxy;
	GdaDataProxy      *proxy;
	GdaDataModelIter  *iter;
	GdauiDataProxyInfoFlag flags; /* ORed values. */

	GtkUIManager      *uimanager;
	GtkActionGroup    *agroup; /* no ref held! */
	guint              merge_id_row_modif;
	guint              merge_id_row_move;
	guint              merge_id_chunck_change;

	GtkWidget         *buttons_bar;
	gboolean           buttons_bar_from_ui;
	GtkWidget         *tool_item;
	GtkWidget         *current_sample;
	GtkWidget         *row_spin;

	guint              idle_id;
};

/* get a pointer to the parents to be able to call their destructor */
static GObjectClass *parent_class = NULL;

/* properties */
enum {
	PROP_0,
	PROP_DATA_PROXY,
	PROP_FLAGS,
	PROP_UI_MANAGER
};

GType
gdaui_data_proxy_info_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0)) {
		static const GTypeInfo info = {
			sizeof (GdauiDataProxyInfoClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) gdaui_data_proxy_info_class_init,
			NULL,
			NULL,
			sizeof (GdauiDataProxyInfo),
			0,
			(GInstanceInitFunc) gdaui_data_proxy_info_init,
			0
		};

		type = g_type_register_static (GTK_TYPE_BOX, "GdauiDataProxyInfo", &info, 0);
	}

	return type;
}

static void
gdaui_data_proxy_info_class_init (GdauiDataProxyInfoClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);


	object_class->dispose = gdaui_data_proxy_info_dispose;

	/* Properties */
        object_class->set_property = gdaui_data_proxy_info_set_property;
        object_class->get_property = gdaui_data_proxy_info_get_property;
	g_object_class_install_property (object_class, PROP_DATA_PROXY,
                                         g_param_spec_object ("data-proxy", NULL, NULL, GDAUI_TYPE_DATA_PROXY,
							      G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (object_class, PROP_FLAGS,
                                         g_param_spec_flags ("flags", NULL, NULL, GDAUI_TYPE_DATA_PROXY_INFO_FLAG,
							     GDAUI_DATA_PROXY_INFO_CURRENT_ROW,
							     G_PARAM_READABLE | G_PARAM_WRITABLE));
	/**
	 * GdauiDataProxyInfo:ui-manager:
	 *
	 * Use this property to obtain the #GtkUIManager object internally used (to add new actions
	 * for example).
	 *
	 * Since: 4.2.9
	 */
	g_object_class_install_property (object_class, PROP_UI_MANAGER,
                                         g_param_spec_object ("ui-manager", NULL, NULL, GTK_TYPE_UI_MANAGER,
							      G_PARAM_READABLE));
}

static void
gdaui_data_proxy_info_init (GdauiDataProxyInfo *wid)
{
	wid->priv = g_new0 (GdauiDataProxyInfoPriv, 1);
	wid->priv->data_proxy = NULL;
	wid->priv->proxy = NULL;
	wid->priv->row_spin = NULL;
	wid->priv->buttons_bar = NULL;
	wid->priv->tool_item = NULL;
}

/**
 * gdaui_data_proxy_info_new:
 * @data_proxy: a widget implementing the #GdauiDataProxy interface
 * @flags: OR'ed values, specifying what to display in the new widget
 *
 * Creates a new #GdauiDataProxyInfo widget suitable to display information about @data_proxy
 *
 * Returns: (transfer full): the new widget
 *
 * Since: 4.2
 */
GtkWidget *
gdaui_data_proxy_info_new (GdauiDataProxy *data_proxy, GdauiDataProxyInfoFlag flags)
{
	GtkWidget *info;

	g_return_val_if_fail (!data_proxy || GDAUI_IS_DATA_PROXY (data_proxy), NULL);

	info = (GtkWidget *) g_object_new (GDAUI_TYPE_DATA_PROXY_INFO,
					   "data-proxy", data_proxy,
					   "flags", flags, NULL);

	return info;
}

static void
data_proxy_destroyed_cb (GdauiDataProxy *wid, GdauiDataProxyInfo *info)
{
	g_assert (wid == info->priv->data_proxy);
	g_signal_handlers_disconnect_by_func (G_OBJECT (wid),
					      G_CALLBACK (data_proxy_destroyed_cb), info);
	g_signal_handlers_disconnect_by_func (G_OBJECT (wid),
					      G_CALLBACK (data_proxy_proxy_changed_cb), info);
	if (GDAUI_IS_RAW_GRID (info->priv->data_proxy))
		g_signal_handlers_disconnect_by_func (info->priv->data_proxy,
						      G_CALLBACK (raw_grid_selection_changed_cb), info);

	info->priv->data_proxy = NULL;
}

static void
release_proxy (GdauiDataProxyInfo *info)
{
	g_signal_handlers_disconnect_by_func (G_OBJECT (info->priv->proxy),
					      G_CALLBACK (proxy_changed_cb), info);
	g_signal_handlers_disconnect_by_func (G_OBJECT (info->priv->proxy),
					      G_CALLBACK (proxy_sample_changed_cb), info);
	g_signal_handlers_disconnect_by_func (G_OBJECT (info->priv->proxy),
					      G_CALLBACK (proxy_row_changed_cb), info);
	g_signal_handlers_disconnect_by_func (G_OBJECT (info->priv->proxy),
					      G_CALLBACK (proxy_reset_cb), info);
	g_object_unref (info->priv->proxy);
	info->priv->proxy = NULL;
}

static void iter_row_changed_cb (GdaDataModelIter *iter, gint row, GdauiDataProxyInfo *info);
static void
release_iter (GdauiDataProxyInfo *info)
{
	g_signal_handlers_disconnect_by_func (info->priv->iter,
					      G_CALLBACK (iter_row_changed_cb), info);
	g_object_unref (info->priv->iter);
	info->priv->iter = NULL;
}

static void
data_proxy_proxy_changed_cb (GdauiDataProxy *data_proxy, G_GNUC_UNUSED GdaDataProxy *proxy, GdauiDataProxyInfo *info)
{
	g_object_set (G_OBJECT (info), "data-proxy", data_proxy, NULL);
}

static void
gdaui_data_proxy_info_dispose (GObject *object)
{
	GdauiDataProxyInfo *info;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GDAUI_IS_DATA_PROXY_INFO (object));
	info = GDAUI_DATA_PROXY_INFO (object);

	if (info->priv) {
		if (info->priv->proxy)
			release_proxy (info);
		if (info->priv->iter)
			release_iter (info);
		if (info->priv->data_proxy)
			data_proxy_destroyed_cb (info->priv->data_proxy, info);
		if (info->priv->idle_id)
			g_source_remove (info->priv->idle_id);

		if (info->priv->uimanager) {
			if (info->priv->merge_id_row_modif)
				gtk_ui_manager_remove_ui (info->priv->uimanager,
							  info->priv->merge_id_row_modif);
			if (info->priv->merge_id_row_move)
				gtk_ui_manager_remove_ui (info->priv->uimanager,
							  info->priv->merge_id_row_move);
			if (info->priv->merge_id_chunck_change)
				gtk_ui_manager_remove_ui (info->priv->uimanager,
							  info->priv->merge_id_chunck_change);
			g_object_unref (info->priv->uimanager);
		}

		/* the private area itself */
		g_free (info->priv);
		info->priv = NULL;
	}

	/* for the parent class */
	parent_class->dispose (object);
}

static void
gdaui_data_proxy_info_set_property (GObject *object,
				    guint param_id,
				    const GValue *value,
				    GParamSpec *pspec)
{
	GdauiDataProxyInfo *info;

        info = GDAUI_DATA_PROXY_INFO (object);
        if (info->priv) {
                switch (param_id) {
                case PROP_DATA_PROXY:
			if (info->priv->data_proxy)
				data_proxy_destroyed_cb (info->priv->data_proxy, info);
			if (info->priv->iter)
				release_iter (info);
			if (info->priv->proxy)
				release_proxy (info);

			info->priv->data_proxy = GDAUI_DATA_PROXY (g_value_get_object (value));
			if (info->priv->data_proxy) {
				GdaDataProxy *proxy;
				GdaDataModelIter *iter;

				/* data widget */
				g_signal_connect (info->priv->data_proxy, "destroy",
						  G_CALLBACK (data_proxy_destroyed_cb), info);
				g_signal_connect (info->priv->data_proxy, "proxy-changed",
						  G_CALLBACK (data_proxy_proxy_changed_cb), info);
				if (GDAUI_IS_RAW_GRID (info->priv->data_proxy))
					g_signal_connect (info->priv->data_proxy, "selection-changed",
							  G_CALLBACK (raw_grid_selection_changed_cb), info);

				/* proxy */
				proxy = gdaui_data_proxy_get_proxy (info->priv->data_proxy);
				if (proxy) {
					info->priv->proxy = proxy;
					g_object_ref (info->priv->proxy);
					g_signal_connect (G_OBJECT (proxy), "changed",
							  G_CALLBACK (proxy_changed_cb), info);
					g_signal_connect (G_OBJECT (proxy), "sample-changed",
							  G_CALLBACK (proxy_sample_changed_cb), info);
					g_signal_connect (G_OBJECT (proxy), "row-inserted",
							  G_CALLBACK (proxy_row_changed_cb), info);
					g_signal_connect (G_OBJECT (proxy), "row-removed",
							  G_CALLBACK (proxy_row_changed_cb), info);
					g_signal_connect (G_OBJECT (proxy), "reset",
							  G_CALLBACK (proxy_reset_cb), info);


					/* iter */
					iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR
											  (info->priv->data_proxy));
					info->priv->iter = iter;
					if (iter) {
						g_object_ref (G_OBJECT (iter));
						g_signal_connect (iter, "row-changed",
								  G_CALLBACK (iter_row_changed_cb), info);
					}
				}
				modif_buttons_update (info);
			}
                        break;
                case PROP_FLAGS:
			info->priv->flags = g_value_get_flags (value);
			modif_buttons_make (info);
			modif_buttons_update (info);
                        break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
			break;
                }
        }
}

static void
gdaui_data_proxy_info_get_property (GObject *object,
				    guint param_id,
				    GValue *value,
				    GParamSpec *pspec)
{
	GdauiDataProxyInfo *info;

        info = GDAUI_DATA_PROXY_INFO (object);
        if (info->priv) {
                switch (param_id) {
		case PROP_DATA_PROXY:
			g_value_set_object (value, info->priv->data_proxy);
			break;
		case PROP_FLAGS:
			g_value_set_flags (value, info->priv->flags);
			break;
		case PROP_UI_MANAGER:
			g_value_set_object (value, info->priv->uimanager);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
			break;
                }
        }
}


static void
proxy_changed_cb (G_GNUC_UNUSED GdaDataProxy *proxy, G_GNUC_UNUSED GdauiDataProxyInfo *info)
{
	modif_buttons_update (info);
}

static void
proxy_sample_changed_cb (G_GNUC_UNUSED GdaDataProxy *proxy, G_GNUC_UNUSED gint sample_start,
			 G_GNUC_UNUSED gint sample_end, G_GNUC_UNUSED GdauiDataProxyInfo *info)
{
	modif_buttons_update (info);
}

static void
proxy_row_changed_cb (G_GNUC_UNUSED GdaDataProxy *proxy, G_GNUC_UNUSED gint row, GdauiDataProxyInfo *info)
{
	modif_buttons_update (info);
}

static void
proxy_reset_cb (G_GNUC_UNUSED GdaDataProxy *wid, GdauiDataProxyInfo *info)
{
	modif_buttons_make (info);
	modif_buttons_update (info);
}

static void
iter_row_changed_cb (G_GNUC_UNUSED GdaDataModelIter *iter, G_GNUC_UNUSED gint row, GdauiDataProxyInfo *info)
{
	modif_buttons_update (info);
}

static void
raw_grid_selection_changed_cb (G_GNUC_UNUSED GdauiRawGrid *grid, GdauiDataProxyInfo *info)
{
	modif_buttons_update (info);
}

/*
 *
 * Modification buttons (Commit changes, Reset info, New entry, Delete)
 *
 */
static const gchar *ui_base =
	"<ui>"
	"  <toolbar name='ToolBar'>"
	"    <placeholder name='RowModif'/>"
	"    <placeholder name='RowModifExtension'/>"
	"    <placeholder name='RowMove'/>"
	"    <placeholder name='ChunkChange'/>"
	"    <toolitem action='ActionFilter'/>"
	"  </toolbar>"
	"</ui>";

static const gchar *ui_row_modif =
	"<ui>"
	"  <toolbar name='ToolBar'>"
	"    <placeholder name='RowModif'>"
	"      <toolitem action='ActionNew'/>"
	"      <toolitem action='ActionDelete'/>"
	"      <toolitem action='ActionCommit'/>"
	"      <toolitem action='ActionReset'/>"
	"    </placeholder>"
	"  </toolbar>"
	"</ui>";
static const gchar *ui_row_move =
	"<ui>"
	"  <toolbar name='ToolBar'>"
	"    <placeholder name='RowMove'>"
	"      <toolitem action='ActionFirstRecord'/>"
	"      <toolitem action='ActionPrevRecord'/>"
	"      <toolitem action='ActionNextRecord'/>"
	"      <toolitem action='ActionLastRecord'/>"
	"    </placeholder>"
	"  </toolbar>"
	"</ui>";
static const gchar *ui_chunck_change =
	"<ui>"
	"  <toolbar name='ToolBar'>"
	"    <placeholder name='ChunkChange'>"
	"      <toolitem action='ActionFirstChunck'/>"
	"      <toolitem action='ActionPrevChunck'/>"
	"      <toolitem action='ActionNextChunck'/>"
	"      <toolitem action='ActionLastChunck'/>"
	"    </placeholder>"
	"  </toolbar>"
	"</ui>";


static void row_spin_changed_cb (GtkSpinButton *spin, GdauiDataProxyInfo *info);
static void
modif_buttons_make (GdauiDataProxyInfo *info)
{
	GtkWidget *wid;
	GdauiDataProxyInfoFlag flags = info->priv->flags;

	if (! info->priv->data_proxy)
		return;

	if (info->priv->tool_item) {
		/* remove tool_item from toolbar */
		gtk_container_remove (GTK_CONTAINER (info->priv->buttons_bar),
				      info->priv->tool_item);
		gtk_widget_unparent (info->priv->tool_item);
	}

	if (info->priv->uimanager) {
		if (info->priv->merge_id_row_modif) {
			gtk_ui_manager_remove_ui (info->priv->uimanager, info->priv->merge_id_row_modif);
			info->priv->merge_id_row_modif = 0;
		}
		if (info->priv->merge_id_row_move) {
			gtk_ui_manager_remove_ui (info->priv->uimanager, info->priv->merge_id_row_move);
			info->priv->merge_id_row_move = 0;
		}
		if (info->priv->merge_id_chunck_change) {
			gtk_ui_manager_remove_ui (info->priv->uimanager, info->priv->merge_id_chunck_change);
			info->priv->merge_id_chunck_change = 0;
		}
		gtk_ui_manager_remove_action_group (info->priv->uimanager, info->priv->agroup);
		info->priv->agroup = NULL;
	}
	else {
		info->priv->uimanager = gtk_ui_manager_new ();	
		gtk_ui_manager_add_ui_from_string (info->priv->uimanager, ui_base, -1, NULL);
	}

	info->priv->agroup = gdaui_data_proxy_get_actions_group (info->priv->data_proxy);
	gtk_ui_manager_insert_action_group (info->priv->uimanager, info->priv->agroup, 0);

	if (flags & (GDAUI_DATA_PROXY_INFO_ROW_MODIFY_BUTTONS |
		     GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS |
		     GDAUI_DATA_PROXY_INFO_CHUNCK_CHANGE_BUTTONS)) {
		GtkUIManager *ui;
		ui = info->priv->uimanager;
		if (flags & GDAUI_DATA_PROXY_INFO_ROW_MODIFY_BUTTONS)
			info->priv->merge_id_row_modif = gtk_ui_manager_add_ui_from_string (ui, ui_row_modif,
											    -1, NULL);
		if (flags & GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS)
			info->priv->merge_id_row_move = gtk_ui_manager_add_ui_from_string (ui, ui_row_move,
											   -1, NULL);
		if (flags & GDAUI_DATA_PROXY_INFO_CHUNCK_CHANGE_BUTTONS)
			info->priv->merge_id_chunck_change = gtk_ui_manager_add_ui_from_string (ui, ui_chunck_change,
												-1, NULL);
	}

	/* get rid of previous toolbar if any */
	if (info->priv->buttons_bar) {
		if (gtk_widget_get_parent (info->priv->buttons_bar)) {
			gtk_container_remove (GTK_CONTAINER (info), info->priv->buttons_bar);
			gtk_widget_unparent (info->priv->buttons_bar);
		}
		if (info->priv->buttons_bar_from_ui)
			g_object_unref (info->priv->buttons_bar);
		else
			gtk_widget_destroy (info->priv->buttons_bar);
		info->priv->buttons_bar = NULL;
	}

	/* create new toolbar */
	GtkUIManager *ui;
	ui = info->priv->uimanager;
	info->priv->buttons_bar = gtk_ui_manager_get_widget (ui, "/ToolBar");
	if (info->priv->buttons_bar)
		info->priv->buttons_bar_from_ui = TRUE;
	else {
		info->priv->buttons_bar = gtk_toolbar_new ();
		info->priv->buttons_bar_from_ui = FALSE;
	}
	g_object_ref_sink (info->priv->buttons_bar);
	gtk_toolbar_set_icon_size (GTK_TOOLBAR (info->priv->buttons_bar), GTK_ICON_SIZE_MENU);
	g_object_set (G_OBJECT (info->priv->buttons_bar), "toolbar-style", GTK_TOOLBAR_ICONS, NULL);
	gtk_box_pack_start (GTK_BOX (info), info->priv->buttons_bar, TRUE, TRUE, 0);
	gtk_widget_show (info->priv->buttons_bar);

	if (flags & GDAUI_DATA_PROXY_INFO_CURRENT_ROW) {
		if (info->priv->tool_item) {
			/* remove the current contents */
			gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (info->priv->tool_item)));
			info->priv->row_spin = NULL;
			info->priv->current_sample = NULL;
			gtk_toolbar_insert (GTK_TOOLBAR (info->priv->buttons_bar),
					    GTK_TOOL_ITEM (info->priv->tool_item), -1);
		}
		else {
			GtkToolItem *ti;
			ti = gtk_tool_item_new  ();
			gtk_toolbar_insert (GTK_TOOLBAR (info->priv->buttons_bar), ti, -1);
			info->priv->tool_item = GTK_WIDGET (g_object_ref (G_OBJECT (ti)));
		}

		GtkWidget *toolwid;
		PangoContext *pc;
		PangoFontDescription *fd, *fdc;
		pc = gtk_widget_get_pango_context (GTK_WIDGET (info));
		fd = pango_context_get_font_description (pc);
		fdc = pango_font_description_copy (fd);
		pango_font_description_set_size (fdc,
						 pango_font_description_get_size (fd) * .8);

		if (flags & GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS) {
			toolwid = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
			
			/* read-write spin counter (mainly for forms) */
			wid = gtk_spin_button_new_with_range (0, 1, 1);

			gtk_widget_override_font (wid, fdc);
			gtk_spin_button_set_digits (GTK_SPIN_BUTTON (wid), 0);
			gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (wid), TRUE);
			gtk_box_pack_start (GTK_BOX (toolwid), wid, FALSE, TRUE, 2);
			gtk_widget_set_sensitive (wid, FALSE);
			info->priv->row_spin = wid;
			g_signal_connect (G_OBJECT (wid), "value-changed",
					  G_CALLBACK (row_spin_changed_cb), info);

			/* rows counter */
			wid = gtk_label_new (" /?");
			gtk_widget_override_font (wid, fdc);
			info->priv->current_sample = wid;
			gtk_box_pack_start (GTK_BOX (toolwid), wid, FALSE, FALSE, 2);
		}
		else {
			/* read-only counter (mainly for grids) */
			wid = gtk_label_new ("? - ? /?");
			gtk_widget_override_font (wid, fdc);
			info->priv->current_sample = wid;
			toolwid = wid;
		}

		pango_font_description_free (fdc);

		gtk_container_add (GTK_CONTAINER (info->priv->tool_item), toolwid);
		gtk_widget_show_all (info->priv->tool_item);
	}
	else if (info->priv->tool_item)
		gtk_widget_hide (info->priv->tool_item);
}

static void
row_spin_changed_cb (GtkSpinButton *spin, GdauiDataProxyInfo *info)
{
	gint row, nrows;
	gint value = gtk_spin_button_get_value (spin);
	nrows = gda_data_model_get_n_rows (GDA_DATA_MODEL (info->priv->proxy));

	if ((value >= 1) && (value <= nrows))
		row = value - 1;
	else if (value > nrows)
		row = nrows - 1;
	else
		row = 0;
	gda_data_model_iter_move_to_row (gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (info->priv->data_proxy)),
					 row);
}

static gboolean idle_modif_buttons_update (GdauiDataProxyInfo *info);
static void
modif_buttons_update (GdauiDataProxyInfo *info)
{
	if (info->priv->idle_id == 0)
		info->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
						       (GSourceFunc) idle_modif_buttons_update,
						       g_object_ref (info), g_object_unref);
}


#define BLOCK_SPIN (g_signal_handlers_block_by_func (G_OBJECT (info->priv->row_spin), \
						     G_CALLBACK (row_spin_changed_cb), info))
#define UNBLOCK_SPIN (g_signal_handlers_unblock_by_func (G_OBJECT (info->priv->row_spin), \
							 G_CALLBACK (row_spin_changed_cb), info))
static gboolean
idle_modif_buttons_update (GdauiDataProxyInfo *info)
{
	GdaDataModelIter *model_iter;
	gboolean wrows, filtered_proxy = FALSE;
	gint row;

	gint proxy_rows = 0;
	gint proxied_rows = 0;
	gint all_rows = 0;

	GtkAction *action;
	gint sample_first_row = 0, sample_last_row = 0, sample_size = 0;
	GdauiDataProxyInfoFlag flags = 0;

	model_iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (info->priv->data_proxy));
	if (info->priv->proxy) {
		filtered_proxy = gda_data_proxy_get_filter_expr (info->priv->proxy) ? TRUE : FALSE;
		proxy_rows = gda_data_model_get_n_rows (GDA_DATA_MODEL (info->priv->proxy));
		if (filtered_proxy) {
			proxied_rows = gda_data_proxy_get_filtered_n_rows (info->priv->proxy);
			all_rows = proxied_rows;
		}
		else {
			proxied_rows = gda_data_proxy_get_proxied_model_n_rows (info->priv->proxy);
			all_rows = proxied_rows + gda_data_proxy_get_n_new_rows (info->priv->proxy);
		}

		/* samples don't take into account the proxy's inserted rows */
		sample_first_row = gda_data_proxy_get_sample_start (info->priv->proxy);
		sample_last_row = gda_data_proxy_get_sample_end (info->priv->proxy);
		sample_size = gda_data_proxy_get_sample_size (info->priv->proxy);

		flags = gda_data_model_get_access_flags (GDA_DATA_MODEL (info->priv->proxy));
	}

	/* sensitiveness of the text indications and of the spin button */
	wrows = (proxy_rows <= 0) ? FALSE : TRUE;
	row = model_iter ? gda_data_model_iter_get_row (model_iter) : 0;
	if (info->priv->flags & GDAUI_DATA_PROXY_INFO_CURRENT_ROW) {
		if (proxy_rows < 0) {
			if (info->priv->flags & GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS) {
				BLOCK_SPIN;
				gtk_spin_button_set_range (GTK_SPIN_BUTTON (info->priv->row_spin), 0, 1);
				gtk_spin_button_set_value (GTK_SPIN_BUTTON (info->priv->row_spin), 0);
				UNBLOCK_SPIN;
				gtk_label_set_text (GTK_LABEL (info->priv->current_sample), " /?");
			}
			else
				gtk_label_set_text (GTK_LABEL (info->priv->current_sample), "? - ? /?");
		}
		else {
			gchar *str;
			gint total;

			total = sample_first_row + proxy_rows;
			if (info->priv->flags & GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS) {
				if (total <= 0)
					str = g_strdup (" / 0");
				else {
					if (filtered_proxy)
						str = g_strdup_printf (" / (%d)", proxy_rows);
					else
						str = g_strdup_printf (" / %d", proxy_rows);
				}
				BLOCK_SPIN;
				gtk_spin_button_set_range (GTK_SPIN_BUTTON (info->priv->row_spin),
							   proxy_rows > 0 ? 1 : 0,
							   proxy_rows);
				if (row >= 0)
					if (gtk_spin_button_get_value (GTK_SPIN_BUTTON (info->priv->row_spin)) != row+1)
						gtk_spin_button_set_value (GTK_SPIN_BUTTON (info->priv->row_spin), row+1);
				UNBLOCK_SPIN;
			}
			else {
				if (total <= 0)
					str = g_strdup_printf ("0 - 0 / 0");
				else {
					if (all_rows < 0)
						str = g_strdup_printf ("%d - %d /?", sample_first_row + 1, total);
					else {
						if (filtered_proxy)
							str = g_strdup_printf ("%d - %d / (%d)",
									       sample_first_row + 1, total, all_rows);
						else
							str = g_strdup_printf ("%d - %d / %d",
									       sample_first_row + 1, total, all_rows);
					}
				}
			}

			gtk_label_set_text (GTK_LABEL (info->priv->current_sample), str);
			g_free (str);
		}

		gtk_widget_set_sensitive (info->priv->current_sample, wrows);
		if (info->priv->row_spin)
			gtk_widget_set_sensitive (info->priv->row_spin, wrows && (row >= 0));
	}

	/* current row modifications */
	if (info->priv->buttons_bar) {
		gboolean changed = FALSE;
		gboolean to_be_deleted = FALSE;
		gboolean is_inserted = FALSE;
		gboolean force_del_btn = FALSE;
		gboolean force_undel_btn = FALSE;
		gboolean has_selection;

		has_selection = (row >= 0) ? TRUE : FALSE;
		if (info->priv->proxy) {
			changed = gda_data_proxy_has_changed (info->priv->proxy);

			if (has_selection) {
				to_be_deleted = gda_data_proxy_row_is_deleted (info->priv->proxy, row);
				is_inserted = gda_data_proxy_row_is_inserted (info->priv->proxy, row);
			}
			else
				if (GDAUI_IS_RAW_GRID (info->priv->data_proxy)) {
					/* bad for encapsulation, but very useful... */
					GList *sel, *list;

					sel = _gdaui_raw_grid_get_selection ((GdauiRawGrid*) info->priv->data_proxy);
					if (sel) {
						list = sel;
						while (list && (!force_del_btn || !force_undel_btn)) {
							if ((GPOINTER_TO_INT (list->data) != -1) &&
							    gda_data_proxy_row_is_deleted (info->priv->proxy,
											   GPOINTER_TO_INT (list->data)))
								force_undel_btn = TRUE;
							else
								force_del_btn = TRUE;
							list = g_list_next (list);
						}
						list = sel;

						is_inserted = TRUE;
						while (list && is_inserted) {
							if (GPOINTER_TO_INT (list->data) != -1)
								is_inserted = FALSE;
							list = g_list_next (list);
						}
						g_list_free (sel);
					}
				}
		}

		if (info->priv->flags & GDAUI_DATA_PROXY_INFO_ROW_MODIFY_BUTTONS) {
			GdauiDataProxyWriteMode mode;
			mode = gdaui_data_proxy_get_write_mode (info->priv->data_proxy);

			action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowModif/ActionCommit");
			g_object_set (G_OBJECT (action), "sensitive", changed ? TRUE : FALSE, NULL);
			if (mode == GDAUI_DATA_PROXY_WRITE_ON_VALUE_CHANGE)
				gtk_action_set_visible (action, FALSE);
			else
				gtk_action_set_visible (action, TRUE);

			action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowModif/ActionReset");
			g_object_set (G_OBJECT (action), "sensitive", changed ? TRUE : FALSE, NULL);
			if (mode == GDAUI_DATA_PROXY_WRITE_ON_VALUE_CHANGE)
				gtk_action_set_visible (action, FALSE);
			else
				gtk_action_set_visible (action, TRUE);

			action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowModif/ActionNew");
			g_object_set (G_OBJECT (action), "sensitive",
				      flags & GDA_DATA_MODEL_ACCESS_INSERT ? TRUE : FALSE, NULL);

			action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowModif/ActionDelete");
			gtk_action_block_activate (action);
			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), to_be_deleted);
			gtk_action_unblock_activate (action);

			if (to_be_deleted) {
				wrows = (flags & GDA_DATA_MODEL_ACCESS_DELETE) &&
					(force_undel_btn || has_selection);
				g_object_set (G_OBJECT (action), "sensitive", wrows, NULL);
				gtk_action_set_tooltip (action, _("Undelete the selected entry"));
			}
			else {
				wrows = is_inserted ||
					((flags & GDA_DATA_MODEL_ACCESS_DELETE) &&
					 (force_del_btn || has_selection));
				g_object_set (G_OBJECT (action), "sensitive", wrows, NULL);
				gtk_action_set_tooltip (action, _("Delete the selected entry"));
			}

			if ((mode == GDAUI_DATA_PROXY_WRITE_ON_ROW_CHANGE) ||
			    (mode == GDAUI_DATA_PROXY_WRITE_ON_VALUE_CHANGE) ||
			    (mode == GDAUI_DATA_PROXY_WRITE_ON_VALUE_ACTIVATED))
				gtk_action_set_visible (action, FALSE);
			else
				gtk_action_set_visible (action, TRUE);
		}
	}

	/* current row moving */
	if (info->priv->flags & GDAUI_DATA_PROXY_INFO_ROW_MOVE_BUTTONS) {
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowMove/ActionFirstRecord");
		g_object_set (G_OBJECT (action), "sensitive", (row <= 0) ? FALSE : TRUE, NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowMove/ActionPrevRecord");
		g_object_set (G_OBJECT (action), "sensitive", (row <= 0) ? FALSE : TRUE, NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowMove/ActionNextRecord");
		g_object_set (G_OBJECT (action), "sensitive", (row == proxy_rows -1) || (row < 0) ? FALSE : TRUE,
			      NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/RowMove/ActionLastRecord");
		g_object_set (G_OBJECT (action), "sensitive", (row == proxy_rows -1) || (row < 0) ? FALSE : TRUE,
			      NULL);
	}

	/* chunck indications */
	if (info->priv->flags & GDAUI_DATA_PROXY_INFO_CHUNCK_CHANGE_BUTTONS) {
		gboolean abool;
		wrows = (sample_size > 0) ? TRUE : FALSE;
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/ChunkChange/ActionFirstChunck");
		g_object_set (G_OBJECT (action), "sensitive", wrows && sample_first_row > 0 ? TRUE : FALSE, NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/ChunkChange/ActionPrevChunck");
		g_object_set (G_OBJECT (action), "sensitive", wrows && sample_first_row > 0 ? TRUE : FALSE, NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/ChunkChange/ActionNextChunck");
		abool = (proxied_rows != -1) ? wrows && (sample_last_row < proxied_rows - 1) : TRUE;
		g_object_set (G_OBJECT (action), "sensitive", abool, NULL);
		action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/ChunkChange/ActionLastChunck");
		g_object_set (G_OBJECT (action), "sensitive", wrows && (sample_last_row < proxied_rows - 1), NULL);
	}

	/* filter */
	action = gtk_ui_manager_get_action (info->priv->uimanager, "/ToolBar/ActionFilter");
	if (info->priv->flags & GDAUI_DATA_PROXY_INFO_NO_FILTER)
		g_object_set (G_OBJECT (action), "visible", FALSE, NULL);
	else
		g_object_set (G_OBJECT (action), "visible", TRUE, NULL);

	if (info->priv->uimanager)
		gtk_ui_manager_ensure_update (info->priv->uimanager);

	/* remove IDLE */
	info->priv->idle_id = 0;
	return FALSE;
}
