Logo Search packages:      
Sourcecode: guitarix version File versions  Download package

GtkFastMeter.cpp

/*
 * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * -------------------------------------------------------------------------
 *
 * Code shamelessly stolen from Ardour by Paul Davis, thanks man!
 * This is actually an adaptation of the C++ gtkmm2ext class in the
 * GTK C style.
 *
 * -------------------------------------------------------------------------
 */
#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;

/* ----- fast meter widget type ----- */
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;
}

/* ----- fast meter class init ----- */
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;
}

/* ----- fast meter init ----- */
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);
}

/* ----- create pixbuf for vertical meter ------ */
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;
}

/* ------ hold count ----- */
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));
}

/* ------- on size request ------- */
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;
}

/* ------- on size allocate ------- */
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);
}


/* ------- widget realize -------- */
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);
}

/* ------- expose event -------- */
gboolean gtk_fast_meter_expose_event (GtkWidget* wd, GdkEventExpose* ev)
{
      GtkFastMeter* fm = GTK_FAST_METER(wd);
      return vertical_expose (fm ,ev);
}

/* ------- setting meter level ----------- */
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;/*gtk_widget_get_window(GTK_WIDGET(fm));*/;
      if (window == 0)
      {
            gtk_widget_queue_draw(GTK_WIDGET(fm));
            return;
      }

      queue_vertical_redraw(fm, window, old_level);
}

/* ------------- clear fast meter object ------------ */
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);
}

/* ------------------------------ static functions ------------------------- */

/* ------- vertical expose event ----------- */
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);

      /* reset the height & origin of the rect that needs to show the pixbuf */

      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;/*gtk_widget_get_window(GTK_WIDGET(fm));*/
            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))
      {
            // draw the part of the meter image that we need.
            // the area we draw is bounded "in reverse" (top->bottom)
            GdkWindow*   window = GTK_WIDGET(fm)->window;/*gtk_widget_get_window(GTK_WIDGET(fm));*/
            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);
      }

      // draw peak bar

      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;
}

/* --------- vertical drawing queue ----------- */
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)
      {
            /* colored/pixbuf got larger, just draw the new section */
            /* rect.y stays where it is because of X coordinates */
            /* height of invalidated area is between new.y (smaller) and old.y
               (larger).
               X coordinates just make my brain hurt.
            */
            rect.height = fm->pixrect.y - rect.y;
      }
      else
      {
            /* it got smaller, compute the difference */
            /* rect.y becomes old.y (the smaller value) */
            rect.y = fm->pixrect.y;
            /* rect.height is the old.y (smaller) minus the new.y (larger) */
            rect.height = fm->pixrect.height - rect.height;
      }


      GdkRegion* region = 0;
      bool queue = false;

      if (rect.height != 0)
      {
            /* ok, first region to draw ... */
            region = gdk_region_rectangle (&rect);
            queue = true;
      }

      /* redraw the last place where the last peak hold bar was;
         the next expose will draw the new one whether its part of
         expose region or not. */

      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);

      // avoid mem leak
      if (region)
      {
            gdk_region_destroy(region);
            region = 0;
      }
}

/* -------------- */
/* EOF */


Generated by  Doxygen 1.6.0   Back to index