/*
    Copyright (C) 2005 Paul Davis
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: pixscroller.cc,v 1.5 2005/09/23 13:19:15 sampo Exp $
*/
#include <iostream>
#include <algorithm>
#include <cmath>

#include <gtk--/style.h>
#include <gtkmmext/pixscroller.h>

using namespace std;
using namespace Gtk;
using namespace Gtkmmext;

PixScroller::PixScroller (Adjustment& a, Pix& pix)
	: adj (a)
{
	dragging = false;
	add_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|GDK_POINTER_MOTION_MASK);

	adj.value_changed.connect (slot (*this, &PixScroller::adjustment_changed));
	default_value = adj.get_value();

	pix.generate (0, 0);

	rail = pix.pixmap (0);
	rail_mask = pix.shape_mask (0);
	slider = pix.pixmap (1);
	slider_mask = pix.shape_mask (1);

	gint w, h;

	gdk_window_get_size (slider, &w, &h);
	sliderrect.width = w;
	sliderrect.height = h;
	gdk_window_get_size (rail, &w, &h);
	railrect.width = w;
	railrect.height = h;

	railrect.y = sliderrect.height / 2;
	sliderrect.x = 0;

	overall_height = railrect.height + sliderrect.height;

	sliderrect.y = (int) rint ((overall_height - sliderrect.height) * (adj.get_upper() - adj.get_value()));
	railrect.x = (sliderrect.width / 2) - 2;
}

void
PixScroller::size_request_impl (GtkRequisition* requisition)
{
	requisition->width = sliderrect.width;
	requisition->height = overall_height;
}

gint
PixScroller::expose_event_impl (GdkEventExpose* ev)
{
	GdkRectangle intersect;
	Gdk_Window win (get_window());

	win.draw_rectangle (get_style()->get_bg_gc(get_state()), TRUE, 
			    ev->area.x,
			    ev->area.y,
			    ev->area.width,
			    ev->area.height);

	if (gdk_rectangle_intersect (&railrect, &ev->area, &intersect)) {
		Gdk_GC gc = get_style()->get_bg_gc(get_state());
		win.draw_pixmap (gc, Gdk_Pixmap (rail), 
				 intersect.x - railrect.x,
				 intersect.y - railrect.y,
				 intersect.x, 
				 intersect.y, 
				 intersect.width,
				 intersect.height);
	}
	
	if (gdk_rectangle_intersect (&sliderrect, &ev->area, &intersect)) {
		Gdk_GC gc = get_style()->get_fg_gc(get_state());
		Gdk_Bitmap mask (slider_mask);
		GdkGCValues values;

		gc.get_values (values);

		gc.set_clip_origin (sliderrect.x, sliderrect.y);
		gc.set_clip_mask (mask);
		win.draw_pixmap (gc, Gdk_Pixmap (slider), 
				 intersect.x - sliderrect.x,
				 intersect.y - sliderrect.y,
				 intersect.x, 
				 intersect.y, 
				 intersect.width,
				 intersect.height);

		gc.set_clip_origin (values.clip_x_origin, values.clip_y_origin);
		Gdk_Bitmap i_hate_gdk (values.clip_mask);
		gc.set_clip_mask (i_hate_gdk);
	}
	
	return TRUE;
}

gint
PixScroller::button_press_event_impl (GdkEventButton* ev)
{
	switch (ev->button) {
	case 1:
		if (!(ev->state & GDK_SHIFT_MASK)) {
			Gtk::Main::grab_add (*this);
			grab_y = ev->y;
			grab_start = ev->y;
			grab_window = ev->window;
			dragging = true;
		}
		break;
	default:
		break;
	} 
			       

	return FALSE;
}

gint
PixScroller::button_release_event_impl (GdkEventButton* ev)
{
	double scale;
	
	if (ev->state & GDK_CONTROL_MASK) {
		if (ev->state & GDK_MOD1_MASK) {
			scale = 0.05;
		} else {
			scale = 0.1;
		}
	} else {
		scale = 1.0;
	}

	switch (ev->button) {
	case 1:
		if (dragging) {
			Gtk::Main::grab_remove (*this);
			dragging = false;

			if (ev->y == grab_start) {
				/* no motion - just a click */
				double fract;

				if (ev->y < sliderrect.height/2) {
					/* near the top */
					fract = 1.0;
				} else {
					fract = 1.0 - (ev->y - sliderrect.height/2) / railrect.height;
				}

				fract = min (1.0, fract);
				fract = max (0.0, fract);

				adj.set_value (scale * fract * (adj.get_upper() - adj.get_lower()));
			}
				
		} else { 
			if (ev->state & GDK_SHIFT_MASK) {
				adj.set_value (default_value);
				cerr << "default value = " << default_value << endl;
			}
		}
		break;
	case 4:
		/* wheel up */
		adj.set_value (adj.get_value() + (adj.get_page_increment() * scale));
		break;
	case 5:
		/* wheel down */
		adj.set_value (adj.get_value() - (adj.get_page_increment() * scale));
		break;
	default:
		break;
	}
	return FALSE;
}

gint
PixScroller::motion_notify_event_impl (GdkEventMotion* ev)
{
	if (dragging) {
		double fract;
		double delta;
		double scale;

		if (ev->window != grab_window) {
			grab_y = ev->y;
			grab_window = ev->window;
			return TRUE;
		}
		
		if (ev->state & GDK_CONTROL_MASK) {
			if (ev->state & GDK_MOD1_MASK) {
				scale = 0.05;
			} else {
				scale = 0.1;
			}
		} else {
			scale = 1.0;
		}

		delta = ev->y - grab_y;
		grab_y = ev->y;

		fract = (delta / railrect.height);

		fract = min (1.0, fract);
		fract = max (-1.0, fract);
		
		fract = -fract;

		adj.set_value (adj.get_value() + scale * fract * (adj.get_upper() - adj.get_lower()));
	}

	return TRUE;
}

void
PixScroller::adjustment_changed ()
{
	int y = (int) rint ((overall_height - sliderrect.height) * (adj.get_upper() - adj.get_value()));

	if (y != sliderrect.y) {
		sliderrect.y = y;
		queue_draw ();
	}
}
