/*
    Copyright (C) 2003 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: fastmeter.cc,v 1.10 2005/04/18 19:08:06 pauld Exp $
*/

#include <iostream>
#include <cmath>
#include <algorithm>
#include <gtkmmext/fastmeter.h>
#include <gtk--/style.h>

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

GdkPixmap *FastMeter::v_pixmap = 0;
GdkBitmap *FastMeter::v_mask = 0;
gint       FastMeter::v_pixheight = 0;
gint       FastMeter::v_pixwidth = 0;

GdkPixmap *FastMeter::h_pixmap = 0;
GdkBitmap *FastMeter::h_mask = 0;
gint       FastMeter::h_pixheight = 0;
gint       FastMeter::h_pixwidth = 0;

FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o)
{
	orientation = o;
	hold_cnt = hold;
	hold_state = 0;
	current_peak = 0;
	current_level = 0;
	current_user_level = -100.0f;
	
	set_events (GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
	
	pixrect.x = 0;
	pixrect.y = 0;

	if (orientation == Vertical) {
		pixrect.width = min (v_pixwidth, (gint) dimen);
		pixrect.height = v_pixheight;
	} else {
		pixrect.width = h_pixwidth;
		pixrect.height = min (h_pixheight, (gint) dimen);
	}

	request_width = pixrect.width;
	request_height = pixrect.height;

	backing = 0;
}

FastMeter::~FastMeter ()
{
	if (backing) {
		gdk_pixmap_unref (backing);
	}
}

void
FastMeter::set_vertical_xpm (const char **xpm)
{
	if (v_pixmap == 0) {
		gint w, h;
		
		v_pixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL,  gdk_colormap_get_system(),
								  &v_mask, NULL, (gchar **) xpm);

		gdk_window_get_size (v_pixmap, &w, &h);
		
		v_pixheight = h;
		v_pixwidth = w;
	}
}

void
FastMeter::set_horizontal_xpm (const char **xpm)
{
	if (h_pixmap == 0) {
		gint w, h;
		
		h_pixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL,  gdk_colormap_get_system(),
								  &h_mask, NULL, (gchar **) xpm);

		gdk_window_get_size (h_pixmap, &w, &h);
		
		h_pixheight = h;
		h_pixwidth = w;
	}
}

void
FastMeter::set_hold_count (long val)
{
	if (val < 1) {
		val = 1;
	}
	
	hold_cnt = val;
	hold_state = 0;
	current_peak = 0;
	
	queue_draw ();
}

void
FastMeter::size_request_impl (GtkRequisition* req)
{
	req->width = request_width;
	req->height = request_height;
}

gint
FastMeter::expose_event_impl (GdkEventExpose* ev)
{
	if (orientation == Vertical) {
		return vertical_expose (ev);
	} else {
		return horizontal_expose (ev);
	}
}

gint
FastMeter::vertical_expose (GdkEventExpose* ev)
{
	GdkRectangle intersect;
	gint top_of_meter;
	int blit = FALSE;

	top_of_meter = (gint) floor (v_pixheight * current_level);
	pixrect.height = v_pixheight - top_of_meter; /* bottom of background */

	if (backing == 0) {
		backing = gdk_pixmap_new (get_window(), width(), height(), -1);
	}

	if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersect)) {

		/* paint the background (black). */
		
		gdk_draw_rectangle (backing, get_style()->get_black_gc(), TRUE, 
				    intersect.x, intersect.y, intersect.width, intersect.height);
		blit = TRUE;
	}
	
	pixrect.height = top_of_meter;

	if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersect)) {

		/* draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
		 */
		
		gdk_draw_pixmap (backing, get_style()->get_fg_gc(get_state()), 
				 v_pixmap,
				 intersect.x, v_pixheight - top_of_meter,
				 intersect.x, v_pixheight - top_of_meter,
				 intersect.width, intersect.height);
		
		blit = TRUE;
	}

	/* draw peak bar */
		
	if (hold_state) {
		gdk_draw_pixmap (backing, get_style()->get_fg_gc(get_state()), 
				 v_pixmap,
				 intersect.x, v_pixheight - (gint) floor (v_pixheight * current_peak),
				 intersect.x, v_pixheight - (gint) floor (v_pixheight * current_peak),
				 intersect.width, 3);
	}

	/* bilt to on-screen drawable */

	if (blit) {
		gdk_draw_pixmap (get_window(), get_style()->get_fg_gc(get_state()), 
				 backing,
				 ev->area.x, ev->area.y,
				 ev->area.x, ev->area.y,
				 ev->area.width, ev->area.height);
	}

	return TRUE;
}

gint
FastMeter::horizontal_expose (GdkEventExpose* ev)
{
	GdkRectangle intersect;
	gint right_of_meter;
	int blit = FALSE;

	right_of_meter = (gint) floor (h_pixwidth * current_level);

	pixrect.x = right_of_meter;
	pixrect.y = 0;
	pixrect.width = h_pixwidth - right_of_meter; 
	
	if (backing == 0) {
		backing = gdk_pixmap_new (get_window(), width(), height(), -1);
	}

	if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersect)) {

		/* paint the background (black). */
		
		gdk_draw_rectangle (backing, get_style()->get_black_gc(), TRUE, 
				    intersect.x, intersect.y, intersect.width, intersect.height);
		blit = TRUE;
	}
	
	pixrect.x = 0;
	pixrect.width = right_of_meter;

	if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersect)) {

		/* draw the part of the meter image that we need. 
		 */

		gdk_draw_pixmap (backing, get_style()->get_fg_gc(get_state()), 
				 h_pixmap,
				 intersect.x, intersect.y,
				 intersect.x, intersect.y,
				 intersect.width, intersect.height);
		
		blit = TRUE;
	}

	/* draw peak bar */
		
	if (hold_state) {
		gdk_draw_pixmap (backing, get_style()->get_fg_gc(get_state()), 
				 h_pixmap,
				 right_of_meter, intersect.y,
				 right_of_meter, intersect.y,
				 3, intersect.height);
	}

	/* bilt to on-screen drawable */

	if (blit) {
		gdk_draw_pixmap (get_window(), get_style()->get_fg_gc(get_state()), 
				 backing,
				 ev->area.x, ev->area.y,
				 ev->area.x, ev->area.y,
				 ev->area.width, ev->area.height);
	}

	return TRUE;
}

void
FastMeter::set (float lvl, float usrlvl)
{
	current_level = lvl;
	current_user_level = usrlvl;
	
	if (lvl > current_peak) {
		current_peak = lvl;
		hold_state = hold_cnt;
	}
	
	if (hold_state > 0) {
		if (--hold_state == 0) {
			current_peak = lvl;
		}
	}

	queue_draw ();
}

void
FastMeter::clear ()
{
	current_level = 0;
	current_peak = 0;
	hold_state = 0;
	queue_draw ();
}
