/* This file is part of GQ
 *
 * AUTHORS
 *     Sven Herzberg  <herzi@gnome-de.org>
 *
 * Copyright (C) 2006  Sven Herzberg
 *
 * 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; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * 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 Lesser 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include "gq-adjustment.h"

#include <glib/gi18n.h>

struct GqAdjustmentPrivate {
	GtkWidget* scrollbar1;
	GtkWidget* scrollbar2;

	GtkAdjustment* adj1;
	GtkAdjustment* adj2;

	gboolean updating : 1;
};
#define P(i) (G_TYPE_INSTANCE_GET_PRIVATE((i), GQ_TYPE_ADJUSTMENT, struct GqAdjustmentPrivate))

GqAdjustment*
gq_adjustment_new(GtkScrollbar* scrollbar1 G_GNUC_UNUSED,
		  GtkScrollbar* scrollbar2 G_GNUC_UNUSED)
{
	return g_object_new(GQ_TYPE_ADJUSTMENT,
			    "scrollbar1", scrollbar1,
			    "scrollbar2", scrollbar2,
			    NULL);
}

/* GType */
G_DEFINE_TYPE(GqAdjustment, gq_adjustment, G_TYPE_OBJECT);

enum {
	PROP_0,
	PROP_ADJUSTMENT_1,
	PROP_ADJUSTMENT_2,
	PROP_SCROLLBAR_1,
	PROP_SCROLLBAR_2
};

static void
gq_adjustment_init(GqAdjustment* self G_GNUC_UNUSED)
{}

static void
adjustment_get_property(GObject   * object,
			guint       prop_id,
			GValue    * value,
			GParamSpec* pspec)
{
	switch(prop_id) {
	case PROP_SCROLLBAR_1:
		g_value_set_object(value, P(object)->scrollbar1);
		break;
	case PROP_SCROLLBAR_2:
		g_value_set_object(value, P(object)->scrollbar2);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
adjustment_sync_from_one(GqAdjustment* self)
{
	// FIXME: this is too stupid
	if(GTK_IS_ADJUSTMENT(P(self)->adj2) && !P(self)->updating) {
		P(self)->updating = TRUE;
		gtk_adjustment_set_value(P(self)->adj2, CLAMP(P(self)->adj1->value,
							      P(self)->adj2->lower,
							      P(self)->adj2->upper - P(self)->adj2->page_size));
		P(self)->updating = FALSE;
	}
}

static void
adjustment_update_adj1(GqAdjustment* self)
{
	GtkAdjustment* adj = NULL;

	g_return_if_fail(GQ_IS_ADJUSTMENT(self));

	if(P(self)->scrollbar1) {
		adj = gtk_range_get_adjustment(GTK_RANGE(P(self)->scrollbar1));
	}

	g_return_if_fail(!adj || GTK_IS_ADJUSTMENT(adj));

	if(adj == P(self)->adj1) {
		return;
	}

	if(P(self)->adj1) {
		g_signal_handlers_disconnect_by_func(P(self)->adj1, adjustment_sync_from_one, self);
		g_object_unref(P(self)->adj1);
		P(self)->adj1 = NULL;
	}

	if(adj) {
		P(self)->adj1 = g_object_ref_sink(adj);
		g_signal_connect_swapped(P(self)->adj1, "value-changed",
					 G_CALLBACK(adjustment_sync_from_one), self);
	}

	g_object_notify(G_OBJECT(self), "adjustment1");
}

static void
adjustment_set_scrollbar1(GqAdjustment* self,
			  GtkWidget   * scrollbar)
{
	g_return_if_fail(GQ_IS_ADJUSTMENT(self));
	g_return_if_fail(GTK_IS_SCROLLBAR(scrollbar));

	if(P(self)->scrollbar1 == scrollbar) {
		return;
	}

	if(P(self)->scrollbar1) {
		g_signal_handlers_disconnect_by_func(P(self)->scrollbar1, adjustment_update_adj1, self);
		g_object_unref(P(self)->scrollbar1);
		P(self)->scrollbar1 = NULL;
	}

	if(scrollbar) {
		P(self)->scrollbar1 = g_object_ref_sink(scrollbar);
		g_signal_connect_swapped(P(self)->scrollbar1, "notify::adjustment",
					 G_CALLBACK(adjustment_update_adj1), self);
	}
	adjustment_update_adj1(self);

	g_object_notify(G_OBJECT(self), "scrollbar1");
}

static void
adjustment_sync_from_two(GqAdjustment* self)
{
	// FIXME: this is too stupid
	if(GTK_IS_ADJUSTMENT(P(self)->adj1) && !P(self)->updating) {
		P(self)->updating = TRUE;
		gtk_adjustment_set_value(P(self)->adj1, CLAMP(P(self)->adj2->value,
							      P(self)->adj1->lower,
							      P(self)->adj1->upper - P(self)->adj1->page_size));
		P(self)->updating = FALSE;
	}
}

static void
adjustment_update_adj2(GqAdjustment* self)
{
	GtkAdjustment* adj = NULL;

	g_return_if_fail(GQ_IS_ADJUSTMENT(self));

	if(P(self)->scrollbar2) {
		adj = gtk_range_get_adjustment(GTK_RANGE(P(self)->scrollbar2));
	}

	g_return_if_fail(!adj || GTK_IS_ADJUSTMENT(adj));

	if(adj == P(self)->adj2) {
		return;
	}

	if(P(self)->adj2) {
		g_signal_handlers_disconnect_by_func(P(self)->adj2, adjustment_sync_from_two, self);
		g_object_unref(P(self)->adj2);
		P(self)->adj2 = NULL;
	}

	if(adj) {
		P(self)->adj2 = g_object_ref_sink(adj);
		g_signal_connect_swapped(P(self)->adj2, "value-changed",
					 G_CALLBACK(adjustment_sync_from_two), self);
	}
	adjustment_sync_from_one(self);

	g_object_notify(G_OBJECT(self), "adjustment2");
}

static void
adjustment_set_scrollbar2(GqAdjustment* self,
			  GtkWidget   * scrollbar)
{
	g_return_if_fail(GQ_IS_ADJUSTMENT(self));
	g_return_if_fail(GTK_IS_SCROLLBAR(scrollbar));

	if(P(self)->scrollbar2 == scrollbar) {
		return;
	}

	if(P(self)->scrollbar2) {
		g_signal_handlers_disconnect_by_func(P(self)->scrollbar2, adjustment_update_adj2, self);
		g_object_unref(P(self)->scrollbar2);
		P(self)->scrollbar2 = NULL;
	}

	if(scrollbar) {
		P(self)->scrollbar2 = g_object_ref_sink(scrollbar);
		g_signal_connect_swapped(P(self)->scrollbar2, "notify::adjustment",
					 G_CALLBACK(adjustment_update_adj2), self);
	}
	adjustment_update_adj2(self);

	g_object_notify(G_OBJECT(self), "scrollbar2");
}

static void
adjustment_dispose(GObject* object)
{
	adjustment_set_scrollbar1(GQ_ADJUSTMENT(object), NULL);
	adjustment_set_scrollbar2(GQ_ADJUSTMENT(object), NULL);

	G_OBJECT_CLASS(gq_adjustment_parent_class)->dispose(object);
}

static void
adjustment_set_property(GObject     * object,
			guint         prop_id,
			GValue const* value,
			GParamSpec  * pspec)
{
	switch(prop_id) {
	case PROP_SCROLLBAR_1:
		adjustment_set_scrollbar1(GQ_ADJUSTMENT(object), g_value_get_object(value));
		break;
	case PROP_SCROLLBAR_2:
		adjustment_set_scrollbar2(GQ_ADJUSTMENT(object), g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}
}

static void
gq_adjustment_class_init(GqAdjustmentClass* self_class)
{
	GObjectClass* object_class = G_OBJECT_CLASS(self_class);

	/* GObjectClass */
	object_class->dispose      = adjustment_dispose;
	object_class->get_property = adjustment_get_property;
	object_class->set_property = adjustment_set_property;

	g_object_class_install_property(object_class, PROP_ADJUSTMENT_1,
					g_param_spec_object("adjustment1",
							    _("Adjustment 1"),
							    _("The first adjustment to be synchronized"),
							    GTK_TYPE_ADJUSTMENT, 0));
	g_object_class_install_property(object_class, PROP_ADJUSTMENT_2,
					g_param_spec_object("adjustment2",
							    _("Adjustment 2"),
							    _("The second adjustment to be synchronized"),
							    GTK_TYPE_ADJUSTMENT, 0));
	g_object_class_install_property(object_class, PROP_SCROLLBAR_1,
					g_param_spec_object("scrollbar1",
							    _("Scrollbar 1"),
							    _("The first scrollbar to be synchronized"),
							    GTK_TYPE_SCROLLBAR,
							    G_PARAM_READWRITE));
	g_object_class_install_property(object_class, PROP_SCROLLBAR_2,
					g_param_spec_object("scrollbar2",
							    _("Scrollbar 2"),
							    _("The second scrollbar to be synchronizeded"),
							    GTK_TYPE_SCROLLBAR,
							    G_PARAM_READWRITE));

	/* GqAdjustmentClass */
	g_type_class_add_private(self_class, sizeof(struct GqAdjustmentPrivate));
}

