#include <stdlib.h>
#include <cmath>
#include "GtkFastMeter.h"
#ifndef max
#define max(x,y) (((x)>(y)) ? (x) : (y))
#endif
#ifndef min
#define min(x,y) (((x)<(y)) ? (x) : (y))
#endif
#define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
#define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
int GtkFastMeter::min_v_pixbuf_size = 10;
int GtkFastMeter::max_v_pixbuf_size = 1024;
int GtkFastMeter::rgb0 = 0;
int GtkFastMeter::rgb1 = 0;
int GtkFastMeter::rgb2 = 0;
int GtkFastMeter::rgb3 = 0;
static void gtk_fast_meter_class_init (GtkFastMeterClass*);
static void gtk_fast_meter_init (GtkFastMeter*);
extern gboolean gtk_fast_meter_expose_event (GtkWidget*, GdkEventExpose*);
static void gtk_fast_meter_size_request (GtkWidget*, GtkRequisition*);
static void gtk_fast_meter_size_allocate (GtkWidget*, GtkAllocation*);
static void gtk_fast_meter_realize (GtkWidget*);
static gboolean vertical_expose (GtkFastMeter*, GdkEventExpose*);
static void queue_vertical_redraw (GtkFastMeter*, GdkWindow*, float);
static GdkPixbuf* request_vertical_meter (int w, int h);
static GtkWidgetClass* parent_class = NULL;
GType gtk_fast_meter_get_type(void)
{
static GType fm_type = 0;
if (!fm_type)
{
static const GTypeInfo fm_info =
{
sizeof(GtkFastMeterClass),
NULL,
NULL,
(GClassInitFunc)gtk_fast_meter_class_init,
NULL,
NULL,
sizeof(GtkFastMeter),
0,
(GInstanceInitFunc)gtk_fast_meter_init
};
fm_type =
g_type_register_static(GTK_TYPE_WIDGET,
"GtkFastMeter", &fm_info, (GTypeFlags)0);
}
return fm_type;
}
void gtk_fast_meter_class_init(GtkFastMeterClass* klass)
{
GtkObjectClass* object_class;
GtkWidgetClass* widget_class;
object_class = (GtkObjectClass*)klass;
widget_class = (GtkWidgetClass*)klass;
parent_class =
(GtkWidgetClass*)gtk_type_class(gtk_widget_get_type());
object_class->destroy = gtk_fast_meter_destroy;
widget_class->realize = gtk_fast_meter_realize;
widget_class->size_request = gtk_fast_meter_size_request;
widget_class->expose_event = gtk_fast_meter_expose_event;
widget_class->size_allocate = gtk_fast_meter_size_allocate;
}
void gtk_fast_meter_init (GtkFastMeter* fm)
{
fm->hold_cnt = 0;
fm->hold_state = 0;
fm->current_peak = 0;
fm->current_level = 0;
fm->last_peak_rect.width = 0;
fm->last_peak_rect.height = 0;
GtkFastMeter::rgb0 = 0x00ff00;
GtkFastMeter::rgb1 = 0xffff00;
GtkFastMeter::rgb2 = 0xffaa00;
GtkFastMeter::rgb3 = 0xff0000;
}
GtkWidget* gtk_fast_meter_new (long hold,
unsigned long dimen,
int len,
int clr0,
int clr1,
int clr2,
int clr3)
{
GtkFastMeter* fm;
fm = GTK_FAST_METER(g_object_new(GTK_TYPE_FAST_METER, NULL));
fm->hold_cnt = hold;
GtkFastMeter::rgb0 = clr0;
GtkFastMeter::rgb1 = clr1;
GtkFastMeter::rgb2 = clr2;
GtkFastMeter::rgb3 = clr0;
gtk_widget_set_events(GTK_WIDGET(fm),
GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK);
fm->pixrect.x = 0;
fm->pixrect.y = 0;
if (len == 0) len = 140;
fm->pixbuf = request_vertical_meter(dimen, len);
fm->pixheight = gdk_pixbuf_get_height(fm->pixbuf);
fm->pixwidth = gdk_pixbuf_get_width(fm->pixbuf);
fm->pixrect.width = min(fm->pixwidth, (gint)dimen);
fm->pixrect.height = fm->pixheight;
fm->request_width = fm->pixrect.width;
fm->request_height = fm->pixrect.height;
return GTK_WIDGET (fm);
}
static GdkPixbuf* request_vertical_meter(int width, int height)
{
if (height < GtkFastMeter::min_v_pixbuf_size)
height = GtkFastMeter::min_v_pixbuf_size;
if (height > GtkFastMeter::max_v_pixbuf_size)
height = GtkFastMeter::max_v_pixbuf_size;
GdkPixbuf* ret;
guint8* data = (guint8*)malloc(width*height * 3);
guint8 r,g,b,r0,g0,b0,r1,g1,b1,r2,g2,b2,r3,g3,b3,a;
UINT_TO_RGBA (GtkFastMeter::rgb0, &r0, &g0, &b0, &a);
UINT_TO_RGBA (GtkFastMeter::rgb1, &r1, &g1, &b1, &a);
UINT_TO_RGBA (GtkFastMeter::rgb2, &r2, &g2, &b2, &a);
UINT_TO_RGBA (GtkFastMeter::rgb3, &r3, &g3, &b3, &a);
int knee = (int)floor((float)height *0.996f );
int y;
for (y = 0; y < knee/2; y++)
{
r = (guint8)floor((float)abs(r1 - r0) * (float)y / (float)(knee/2));
(r0 >= r1) ? r = r0 - r : r += r0;
g = (guint8)floor((float)abs(g1 - g0) * (float)y / (float)(knee/2));
(g0 >= g1) ? g = g0 - g : g += g0;
b = (guint8)floor((float)abs(b1 - b0) * (float)y / (float)(knee/2));
(b0 >= b1) ? b = b0 - b : b += b0;
for (int x = 0; x < width; x++)
{
data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
}
}
int offset = knee - y;
for (int i=0; i < offset; i++,y++)
{
r = (guint8)floor((float)abs(r2 - r1) * (float)i / (float)offset);
(r1 >= r2) ? r = r1 - r : r += r1;
g = (guint8)floor((float)abs(g2 - g1) * (float)i / (float)offset);
(g1 >= g2) ? g = g1 - g : g += g1;
b = (guint8)floor((float)abs(b2 - b1) * (float)i / (float)offset);
(b1 >= b2) ? b = b1 - b : b += b1;
for (int x = 0; x < width; x++)
{
data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
}
}
for (; y < height; y++)
{
for (int x = 0; x < width; x++) {
data[ (x+(height-y-1)*width) * 3 + 0 ] = r3;
data[ (x+(height-y-1)*width) * 3 + 1 ] = g3;
data[ (x+(height-y-1)*width) * 3 + 2 ] = b3;
}
}
ret = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, false, 8, width, height, width * 3, NULL, NULL);
return ret;
}
void gtk_fast_meter_set_hold_count(GtkFastMeter* fm, long val)
{
if (val < 1) val = 1;
fm->hold_cnt = val;
fm->hold_state = 0;
fm->current_peak = 0;
gtk_widget_queue_draw(GTK_WIDGET(fm));
}
void gtk_fast_meter_size_request (GtkWidget* wd, GtkRequisition* req)
{
GtkFastMeter* fm = GTK_FAST_METER(wd);
req->height = fm->request_height;
req->height = max(req->height, GtkFastMeter::min_v_pixbuf_size);
req->height = min(req->height, GtkFastMeter::max_v_pixbuf_size);
req->width = fm->request_width;
}
void gtk_fast_meter_size_allocate (GtkWidget* wd, GtkAllocation* alloc)
{
g_return_if_fail (wd != NULL);
g_return_if_fail (GTK_IS_FAST_METER (wd));
g_return_if_fail (alloc != NULL);
GtkFastMeter* fm = GTK_FAST_METER(wd);
if (alloc->width != fm->request_width)
alloc->width = fm->request_width;
int h = alloc->height;
h = max(h, GtkFastMeter::min_v_pixbuf_size);
h = min(h, GtkFastMeter::max_v_pixbuf_size);
if (h != alloc->height)
alloc->height = h;
if (fm->pixheight != h)
fm->pixbuf = request_vertical_meter(fm->request_width, h);
fm->pixheight = gdk_pixbuf_get_height(fm->pixbuf);
fm->pixwidth = gdk_pixbuf_get_width (fm->pixbuf);
wd->allocation = *alloc;
if (GTK_WIDGET_REALIZED (wd))
gdk_window_move_resize (wd->window,
alloc->x, alloc->y, alloc->width, alloc->height);
}
void gtk_fast_meter_realize (GtkWidget* wd)
{
g_return_if_fail (wd != NULL);
g_return_if_fail (GTK_IS_FAST_METER (wd));
GdkWindowAttr attributes;
gint attributes_mask;
GTK_WIDGET_SET_FLAGS (wd, GTK_REALIZED);
attributes.x = wd->allocation.x;
attributes.y = wd->allocation.y;
attributes.width = wd->allocation.width;
attributes.height = wd->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (wd) | GDK_EXPOSURE_MASK;
attributes.visual = gtk_widget_get_visual (wd);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
wd->window = gdk_window_new(wd->parent->window, &attributes, attributes_mask);
wd->style = gtk_style_attach (wd->style, wd->window);
gdk_window_set_user_data (wd->window, wd);
gtk_style_set_background (wd->style, wd->window, GTK_STATE_ACTIVE);
}
gboolean gtk_fast_meter_expose_event (GtkWidget* wd, GdkEventExpose* ev)
{
GtkFastMeter* fm = GTK_FAST_METER(wd);
return vertical_expose (fm ,ev);
}
void gtk_fast_meter_set(GtkFastMeter* fm, float lvl)
{
float old_level = fm->current_level;
float old_peak = fm->current_peak;
fm->current_level = lvl;
if (lvl > fm->current_peak)
{
fm->current_peak = lvl;
fm->hold_state = fm->hold_cnt;
}
if (fm->hold_state > 0)
if (--fm->hold_state == 0)
fm->current_peak = lvl;
if (fm->current_level == old_level &&
fm->current_peak == old_peak &&
fm->hold_state == 0)
return;
GdkWindow* window = GTK_WIDGET(fm)->window;;
if (window == 0)
{
gtk_widget_queue_draw(GTK_WIDGET(fm));
return;
}
queue_vertical_redraw(fm, window, old_level);
}
void gtk_fast_meter_clear(GtkFastMeter* fm)
{
fm->current_level = 0;
fm->current_peak = 0;
fm->hold_state = 0;
gtk_widget_queue_draw(GTK_WIDGET(fm));
}
void gtk_fast_meter_destroy(GtkObject* object)
{
GtkFastMeter* fm;
g_return_if_fail(object != NULL);
g_return_if_fail(GTK_IS_FAST_METER (object));
fm = GTK_FAST_METER(object);
if (GTK_WIDGET(object)->parent &&
GTK_WIDGET_MAPPED(object))
gtk_widget_unmap(GTK_WIDGET(object));
if (GTK_OBJECT_CLASS(parent_class)->destroy)
(*GTK_OBJECT_CLASS(parent_class)->destroy) (object);
}
gboolean vertical_expose (GtkFastMeter* fm, GdkEventExpose* ev)
{
gint top_of_meter;
GdkRectangle intersection;
GdkRectangle background;
top_of_meter = (gint) floor (fm->pixheight * fm->current_level);
fm->pixrect.height = top_of_meter;
fm->pixrect.y = fm->pixheight - top_of_meter;
background.x = 0;
background.y = 0;
background.width = fm->pixrect.width;
background.height = fm->pixheight - top_of_meter;
if (gdk_rectangle_intersect (&background, &ev->area, &intersection))
{
GdkWindow* window = GTK_WIDGET(fm)->window;
GtkStyle* style = gtk_widget_get_style (GTK_WIDGET(fm));
gdk_draw_rectangle(GDK_DRAWABLE(window),
style->black_gc,
TRUE,
intersection.x,
intersection.y,
intersection.width,
intersection.height);
}
if (gdk_rectangle_intersect(&fm->pixrect, &ev->area, &intersection))
{
GdkWindow* window = GTK_WIDGET(fm)->window;
GtkStyle* style = gtk_widget_get_style (GTK_WIDGET(fm));
gdk_draw_pixbuf(GDK_DRAWABLE(window),
style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(fm))],
fm->pixbuf,
intersection.x, intersection.y,
intersection.x, intersection.y,
intersection.width, intersection.height,
GDK_RGB_DITHER_NONE, 0, 0);
}
if (fm->hold_state)
{
fm->last_peak_rect.x = 0;
fm->last_peak_rect.width = fm->pixwidth;
fm->last_peak_rect.y = fm->pixheight -
(gint)floor(fm->pixheight * fm->current_peak);
fm->last_peak_rect.height = min(3, fm->pixheight - fm->last_peak_rect.y);
GdkWindow* window = GTK_WIDGET(fm)->window;
GtkStyle* style = gtk_widget_get_style (GTK_WIDGET(fm));
gdk_draw_pixbuf(GDK_DRAWABLE(window),
style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(fm))],
fm->pixbuf,
0, fm->last_peak_rect.y,
0, fm->last_peak_rect.y,
fm->pixwidth, fm->last_peak_rect.height,
GDK_RGB_DITHER_NONE, 0, 0);
}
else
{
fm->last_peak_rect.width = 0;
fm->last_peak_rect.height = 0;
}
return TRUE;
}
void queue_vertical_redraw (GtkFastMeter* fm,
GdkWindow* win,
float old_level)
{
GdkRectangle rect;
gint new_top = (gint)floor(fm->pixheight * fm->current_level);
rect.x = 0;
rect.width = fm->pixwidth;
rect.height = new_top;
rect.y = fm->pixheight - new_top;
if (fm->current_level > old_level)
{
rect.height = fm->pixrect.y - rect.y;
}
else
{
rect.y = fm->pixrect.y;
rect.height = fm->pixrect.height - rect.height;
}
GdkRegion* region = 0;
bool queue = false;
if (rect.height != 0)
{
region = gdk_region_rectangle (&rect);
queue = true;
}
if (fm->last_peak_rect.width * fm->last_peak_rect.height != 0)
{
if (!queue)
{
region = gdk_region_new ();
queue = true;
}
gdk_region_union_with_rect (region, &fm->last_peak_rect);
}
if (queue)
gdk_window_invalidate_region (win, region, TRUE);
if (region)
{
gdk_region_destroy(region);
region = 0;
}
}