2 Copyright (C) 2003-2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <gdkmm/rectangle.h>
29 #include <gtkmm2ext/fastmeter.h>
30 #include <gtkmm2ext/utils.h>
32 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
33 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
37 using namespace Gtkmm2ext;
40 int FastMeter::min_pattern_metric_size = 16;
41 int FastMeter::max_pattern_metric_size = 1024;
42 bool FastMeter::no_rgba_overlay = false;
44 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
45 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
47 FastMeter::Pattern10Map FastMeter::hm_pattern_cache;
48 FastMeter::PatternBgMap FastMeter::hb_pattern_cache;
50 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
51 int clr0, int clr1, int clr2, int clr3,
52 int clr4, int clr5, int clr6, int clr7,
56 float stp0, float stp1,
57 float stp2, float stp3,
67 last_peak_rect.width = 0;
68 last_peak_rect.height = 0;
71 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
95 _styleflags = styleflags;
97 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
105 if (orientation == Vertical) {
108 fgpattern = request_vertical_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
109 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, _bgc, false);
114 fgpattern = request_horizontal_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
115 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, _bgc, false);
118 pixrect.width = pixwidth;
119 pixrect.height = pixheight;
121 request_width = pixrect.width + 2;
122 request_height= pixrect.height + 2;
127 FastMeter::~FastMeter ()
131 Cairo::RefPtr<Cairo::Pattern>
132 FastMeter::generate_meter_pattern (
133 int width, int height, int *clr, float *stp, int styleflags, bool horiz)
137 const double soft = 2.5 / (double) height;
138 const double offs = -1.0 / (double) height;
140 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
143 Cairo coordinate space goes downwards as y value goes up, so invert
144 knee-based positions by using (1.0 - y)
147 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
148 cairo_pattern_add_color_stop_rgb (pat, 0.0,
149 r/255.0, g/255.0, b/255.0);
151 knee = offs + stp[3] / 115.0f; // -0dB
153 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
154 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
155 r/255.0, g/255.0, b/255.0);
157 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
158 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
159 r/255.0, g/255.0, b/255.0);
161 knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
163 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
164 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
165 r/255.0, g/255.0, b/255.0);
167 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
168 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
169 r/255.0, g/255.0, b/255.0);
171 knee = offs + stp[1] / 115.0f; // -9dB
173 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
174 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
175 r/255.0, g/255.0, b/255.0);
177 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
178 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
179 r/255.0, g/255.0, b/255.0);
181 knee = offs + stp[0] / 115.0f; // -18dB
183 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
184 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
185 r/255.0, g/255.0, b/255.0);
187 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
188 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
189 r/255.0, g/255.0, b/255.0);
191 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
192 cairo_pattern_add_color_stop_rgb (pat, 1.0,
193 r/255.0, g/255.0, b/255.0);
195 if ((styleflags & 1) && !no_rgba_overlay) {
196 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
197 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
198 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
199 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
201 cairo_surface_t* surface;
203 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
204 tc = cairo_create (surface);
205 cairo_set_source (tc, pat);
206 cairo_rectangle (tc, 0, 0, width, height);
209 if (styleflags & 2) { // LED stripes
211 cairo_set_line_width(tc, 1.0);
212 cairo_set_source_rgba(tc, .0, .0, .0, .3);
213 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
214 for (float y=.5; y < height; y+= 2.0) {
215 cairo_move_to(tc, 0, y);
216 cairo_line_to(tc, width, y);
222 cairo_set_source (tc, shade_pattern);
223 cairo_rectangle (tc, 0, 0, width, height);
226 cairo_pattern_destroy (pat);
227 cairo_pattern_destroy (shade_pattern);
229 pat = cairo_pattern_create_for_surface (surface);
232 cairo_surface_destroy (surface);
236 cairo_surface_t* surface;
238 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
239 tc = cairo_create (surface);
242 cairo_matrix_init_rotate (&m, -M_PI/2.0);
243 cairo_matrix_translate (&m, -height, 0);
244 cairo_pattern_set_matrix (pat, &m);
245 cairo_set_source (tc, pat);
246 cairo_rectangle (tc, 0, 0, height, width);
248 cairo_pattern_destroy (pat);
249 pat = cairo_pattern_create_for_surface (surface);
251 cairo_surface_destroy (surface);
253 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
259 Cairo::RefPtr<Cairo::Pattern>
260 FastMeter::generate_meter_background (
261 int width, int height, int *clr, bool shade, bool horiz)
263 guint8 r0,g0,b0,r1,g1,b1,a;
265 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
267 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
268 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
270 cairo_pattern_add_color_stop_rgb (pat, 0.0,
271 r1/255.0, g1/255.0, b1/255.0);
273 cairo_pattern_add_color_stop_rgb (pat, 1.0,
274 r0/255.0, g0/255.0, b0/255.0);
276 if (shade && !no_rgba_overlay) {
277 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
278 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
279 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
280 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
282 cairo_surface_t* surface;
284 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
285 tc = cairo_create (surface);
286 cairo_set_source (tc, pat);
287 cairo_rectangle (tc, 0, 0, width, height);
289 cairo_set_source (tc, shade_pattern);
290 cairo_rectangle (tc, 0, 0, width, height);
293 cairo_pattern_destroy (pat);
294 cairo_pattern_destroy (shade_pattern);
296 pat = cairo_pattern_create_for_surface (surface);
299 cairo_surface_destroy (surface);
303 cairo_surface_t* surface;
305 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
306 tc = cairo_create (surface);
309 cairo_matrix_init_rotate (&m, -M_PI/2.0);
310 cairo_matrix_translate (&m, -height, 0);
311 cairo_pattern_set_matrix (pat, &m);
312 cairo_set_source (tc, pat);
313 cairo_rectangle (tc, 0, 0, height, width);
315 cairo_pattern_destroy (pat);
316 pat = cairo_pattern_create_for_surface (surface);
318 cairo_surface_destroy (surface);
321 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
326 Cairo::RefPtr<Cairo::Pattern>
327 FastMeter::request_vertical_meter(
328 int width, int height, int *clr, float *stp, int styleflags)
330 height = max(height, min_pattern_metric_size);
331 height = min(height, max_pattern_metric_size);
333 const Pattern10MapKey key (width, height,
334 stp[0], stp[1], stp[2], stp[3],
335 clr[0], clr[1], clr[2], clr[3],
336 clr[4], clr[5], clr[6], clr[7],
337 clr[8], clr[9], styleflags);
339 Pattern10Map::iterator i;
340 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
343 // TODO flush pattern cache if it gets too large
345 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
346 width, height, clr, stp, styleflags, false);
347 vm_pattern_cache[key] = p;
352 Cairo::RefPtr<Cairo::Pattern>
353 FastMeter::request_vertical_background(
354 int width, int height, int *bgc, bool shade)
356 height = max(height, min_pattern_metric_size);
357 height = min(height, max_pattern_metric_size);
360 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
361 PatternBgMap::iterator i;
362 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
365 // TODO flush pattern cache if it gets too large
367 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
368 width, height, bgc, shade, false);
369 vb_pattern_cache[key] = p;
374 Cairo::RefPtr<Cairo::Pattern>
375 FastMeter::request_horizontal_meter(
376 int width, int height, int *clr, float *stp, int styleflags)
378 width = max(width, min_pattern_metric_size);
379 width = min(width, max_pattern_metric_size);
381 const Pattern10MapKey key (width, height,
382 stp[0], stp[1], stp[2], stp[3],
383 clr[0], clr[1], clr[2], clr[3],
384 clr[4], clr[5], clr[6], clr[7],
385 clr[8], clr[9], styleflags);
387 Pattern10Map::iterator i;
388 if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
391 // TODO flush pattern cache if it gets too large
393 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
394 height, width, clr, stp, styleflags, true);
396 hm_pattern_cache[key] = p;
400 Cairo::RefPtr<Cairo::Pattern>
401 FastMeter::request_horizontal_background(
402 int width, int height, int *bgc, bool shade)
404 width = max(width, min_pattern_metric_size);
405 width = min(width, max_pattern_metric_size);
408 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
409 PatternBgMap::iterator i;
410 if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
413 // TODO flush pattern cache if it gets too large
415 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
416 height, width, bgc, shade, true);
418 hb_pattern_cache[key] = p;
426 FastMeter::set_hold_count (long val)
440 FastMeter::on_size_request (GtkRequisition* req)
442 if (orientation == Vertical) {
443 vertical_size_request (req);
445 horizontal_size_request (req);
450 FastMeter::vertical_size_request (GtkRequisition* req)
452 req->height = request_height;
453 req->height = max(req->height, min_pattern_metric_size);
454 req->height = min(req->height, max_pattern_metric_size);
457 req->width = request_width;
461 FastMeter::horizontal_size_request (GtkRequisition* req)
463 req->width = request_width;
464 req->width = max(req->width, min_pattern_metric_size);
465 req->width = min(req->width, max_pattern_metric_size);
468 req->height = request_height;
472 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
474 if (orientation == Vertical) {
475 vertical_size_allocate (alloc);
477 horizontal_size_allocate (alloc);
483 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
485 if (alloc.get_width() != request_width) {
486 alloc.set_width (request_width);
489 int h = alloc.get_height();
490 h = max (h, min_pattern_metric_size + 2);
491 h = min (h, max_pattern_metric_size + 2);
493 if (h != alloc.get_height()) {
494 alloc.set_height (h);
497 if (pixheight != h) {
498 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
499 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
501 pixwidth = request_width - 2;
504 DrawingArea::on_size_allocate (alloc);
508 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
510 if (alloc.get_height() != request_height) {
511 alloc.set_height (request_height);
514 int w = alloc.get_width();
515 w = max (w, min_pattern_metric_size + 2);
516 w = min (w, max_pattern_metric_size + 2);
518 if (w != alloc.get_width()) {
523 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
524 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, highlight);
526 pixheight = request_height - 2;
529 DrawingArea::on_size_allocate (alloc);
533 FastMeter::on_expose_event (GdkEventExpose* ev)
535 if (orientation == Vertical) {
536 return vertical_expose (ev);
538 return horizontal_expose (ev);
543 FastMeter::vertical_expose (GdkEventExpose* ev)
545 Glib::RefPtr<Gdk::Window> win = get_window ();
547 GdkRectangle intersection;
548 GdkRectangle background;
550 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
552 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
555 cairo_set_source_rgb (cr, 0, 0, 0); // black
556 rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
559 top_of_meter = (gint) floor (pixheight * current_level);
561 /* reset the height & origin of the rect that needs to show the pixbuf
564 pixrect.height = top_of_meter;
565 pixrect.y = 1 + pixheight - top_of_meter;
569 background.width = pixrect.width;
570 background.height = pixheight - top_of_meter;
572 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
573 cairo_set_source (cr, bgpattern->cobj());
574 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
578 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
579 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
580 cairo_set_source (cr, fgpattern->cobj());
581 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
588 last_peak_rect.x = 1;
589 last_peak_rect.width = pixwidth;
590 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
592 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
594 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
597 cairo_set_source (cr, fgpattern->cobj());
598 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
600 if (bright_hold && !no_rgba_overlay) {
601 cairo_fill_preserve (cr);
602 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
607 last_peak_rect.width = 0;
608 last_peak_rect.height = 0;
617 FastMeter::horizontal_expose (GdkEventExpose* ev)
619 Glib::RefPtr<Gdk::Window> win = get_window ();
621 GdkRectangle intersection;
622 GdkRectangle background;
624 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
626 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
629 cairo_set_source_rgb (cr, 0, 0, 0); // black
630 rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
633 right_of_meter = (gint) floor (pixwidth * current_level);
635 /* reset the height & origin of the rect that needs to show the pixbuf
638 pixrect.width = right_of_meter;
640 background.x = 1 + right_of_meter;
642 background.width = pixwidth - right_of_meter;
643 background.height = pixheight;
645 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
646 cairo_set_source (cr, bgpattern->cobj());
647 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
651 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
652 cairo_set_source (cr, fgpattern->cobj());
653 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
660 last_peak_rect.y = 1;
661 last_peak_rect.height = pixheight;
662 const int xpos = floor (pixwidth * current_peak);
664 last_peak_rect.width = min(4, xpos );
666 last_peak_rect.width = min(2, xpos );
668 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
670 cairo_set_source (cr, fgpattern->cobj());
671 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
673 if (bright_hold && !no_rgba_overlay) {
674 cairo_fill_preserve (cr);
675 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
680 last_peak_rect.width = 0;
681 last_peak_rect.height = 0;
690 FastMeter::set (float lvl, float peak)
692 float old_level = current_level;
693 float old_peak = current_peak;
696 if (lvl >= current_peak) {
698 hold_state = hold_cnt;
701 if (hold_state > 0) {
702 if (--hold_state == 0) {
715 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
719 Glib::RefPtr<Gdk::Window> win;
721 if ((win = get_window()) == 0) {
726 if (orientation == Vertical) {
727 queue_vertical_redraw (win, old_level);
729 queue_horizontal_redraw (win, old_level);
734 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
738 gint new_top = (gint) floor (pixheight * current_level);
741 rect.width = pixwidth;
742 rect.height = new_top;
743 rect.y = 1 + pixheight - new_top;
745 if (current_level > old_level) {
746 /* colored/pixbuf got larger, just draw the new section */
747 /* rect.y stays where it is because of X coordinates */
748 /* height of invalidated area is between new.y (smaller) and old.y
750 X coordinates just make my brain hurt.
752 rect.height = pixrect.y - rect.y;
754 /* it got smaller, compute the difference */
755 /* rect.y becomes old.y (the smaller value) */
757 /* rect.height is the old.y (smaller) minus the new.y (larger)
759 rect.height = pixrect.height - rect.height;
762 GdkRegion* region = 0;
765 if (rect.height != 0) {
767 /* ok, first region to draw ... */
769 region = gdk_region_rectangle (&rect);
773 /* redraw the last place where the last peak hold bar was;
774 the next expose will draw the new one whether its part of
775 expose region or not.
778 if (last_peak_rect.width * last_peak_rect.height != 0) {
780 region = gdk_region_new ();
783 gdk_region_union_with_rect (region, &last_peak_rect);
786 if (hold_state && current_peak > 0) {
788 region = gdk_region_new ();
792 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
794 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
796 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
798 rect.width = pixwidth;
799 gdk_region_union_with_rect (region, &rect);
803 gdk_window_invalidate_region (win->gobj(), region, true);
806 gdk_region_destroy(region);
812 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
816 gint new_right = (gint) floor (pixwidth * current_level);
818 rect.height = pixheight;
821 if (current_level > old_level) {
822 rect.x = 1 + pixrect.width;
823 /* colored/pixbuf got larger, just draw the new section */
824 rect.width = new_right - pixrect.width;
826 /* it got smaller, compute the difference */
827 rect.x = 1 + new_right;
828 /* rect.height is the old.x (smaller) minus the new.x (larger) */
829 rect.width = pixrect.width - new_right;
832 GdkRegion* region = 0;
835 if (rect.height != 0) {
837 /* ok, first region to draw ... */
839 region = gdk_region_rectangle (&rect);
843 /* redraw the last place where the last peak hold bar was;
844 the next expose will draw the new one whether its part of
845 expose region or not.
848 if (last_peak_rect.width * last_peak_rect.height != 0) {
850 region = gdk_region_new ();
853 gdk_region_union_with_rect (region, &last_peak_rect);
856 if (hold_state && current_peak > 0) {
858 region = gdk_region_new ();
862 rect.height = pixheight;
863 const int xpos = floor (pixwidth * current_peak);
865 rect.width = min(4, xpos);
867 rect.width = min(2, xpos);
869 rect.x = 1 + max(0, xpos - rect.width);
870 gdk_region_union_with_rect (region, &rect);
874 gdk_window_invalidate_region (win->gobj(), region, true);
877 gdk_region_destroy(region);
883 FastMeter::set_highlight (bool onoff)
885 if (highlight == onoff) {
889 if (orientation == Vertical) {
890 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
892 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);