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::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
48 int clr0, int clr1, int clr2, int clr3,
49 int clr4, int clr5, int clr6, int clr7,
53 float stp0, float stp1,
54 float stp2, float stp3,
64 last_peak_rect.width = 0;
65 last_peak_rect.height = 0;
68 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
92 _styleflags = styleflags;
94 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
102 fgpattern = request_vertical_meter(dimen, len, _clr, _stp, _styleflags);
103 bgpattern = request_vertical_background (dimen, len, _bgc, false);
107 pixrect.width = pixwidth;
108 pixrect.height = pixheight;
110 request_width = pixrect.width + 2;
111 request_height= pixrect.height + 2;
116 FastMeter::~FastMeter ()
120 Cairo::RefPtr<Cairo::Pattern>
121 FastMeter::generate_meter_pattern (
122 int width, int height, int *clr, float *stp, int styleflags)
126 const double soft = 2.5 / (double) height;
127 const double offs = -1.0 / (double) height;
129 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
132 Cairo coordinate space goes downwards as y value goes up, so invert
133 knee-based positions by using (1.0 - y)
136 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
137 cairo_pattern_add_color_stop_rgb (pat, 0.0,
138 r/255.0, g/255.0, b/255.0);
140 knee = offs + stp[3] / 115.0f; // -0dB
142 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
143 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
144 r/255.0, g/255.0, b/255.0);
146 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
147 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
148 r/255.0, g/255.0, b/255.0);
150 knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
152 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
153 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
154 r/255.0, g/255.0, b/255.0);
156 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
157 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
158 r/255.0, g/255.0, b/255.0);
160 knee = offs + stp[1] / 115.0f; // -9dB
162 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
163 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
164 r/255.0, g/255.0, b/255.0);
166 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
167 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
168 r/255.0, g/255.0, b/255.0);
170 knee = offs + stp[0] / 115.0f; // -18dB
172 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
173 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
174 r/255.0, g/255.0, b/255.0);
176 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
177 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
178 r/255.0, g/255.0, b/255.0);
180 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
181 cairo_pattern_add_color_stop_rgb (pat, 1.0,
182 r/255.0, g/255.0, b/255.0);
184 if ((styleflags & 1) && !no_rgba_overlay) {
185 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
186 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
187 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
188 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
190 cairo_surface_t* surface;
192 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
193 tc = cairo_create (surface);
194 cairo_set_source (tc, pat);
195 cairo_rectangle (tc, 0, 0, width, height);
198 if (styleflags & 2) { // LED stripes
200 cairo_set_line_width(tc, 1.0);
201 cairo_set_source_rgba(tc, .0, .0, .0, .3);
202 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
203 for (float y=.5; y < height; y+= 2.0) {
204 cairo_move_to(tc, 0, y);
205 cairo_line_to(tc, width, y);
211 cairo_set_source (tc, shade_pattern);
212 cairo_rectangle (tc, 0, 0, width, height);
215 cairo_pattern_destroy (pat);
216 cairo_pattern_destroy (shade_pattern);
218 pat = cairo_pattern_create_for_surface (surface);
221 cairo_surface_destroy (surface);
224 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
230 Cairo::RefPtr<Cairo::Pattern>
231 FastMeter::generate_meter_background (
232 int width, int height, int *clr, bool shade)
234 guint8 r0,g0,b0,r1,g1,b1,a;
236 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
238 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
239 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
241 cairo_pattern_add_color_stop_rgb (pat, 0.0,
242 r1/255.0, g1/255.0, b1/255.0);
244 cairo_pattern_add_color_stop_rgb (pat, 1.0,
245 r0/255.0, g0/255.0, b0/255.0);
247 if (shade && !no_rgba_overlay) {
248 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
249 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
250 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
251 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
253 cairo_surface_t* surface;
255 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
256 tc = cairo_create (surface);
257 cairo_set_source (tc, pat);
258 cairo_rectangle (tc, 0, 0, width, height);
260 cairo_set_source (tc, shade_pattern);
261 cairo_rectangle (tc, 0, 0, width, height);
264 cairo_pattern_destroy (pat);
265 cairo_pattern_destroy (shade_pattern);
267 pat = cairo_pattern_create_for_surface (surface);
270 cairo_surface_destroy (surface);
273 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
278 Cairo::RefPtr<Cairo::Pattern>
279 FastMeter::request_vertical_meter(
280 int width, int height, int *clr, float *stp, int styleflags)
282 if (height < min_pattern_metric_size)
283 height = min_pattern_metric_size;
284 if (height > max_pattern_metric_size)
285 height = max_pattern_metric_size;
287 const Pattern10MapKey key (width, height,
288 stp[0], stp[1], stp[2], stp[3],
289 clr[0], clr[1], clr[2], clr[3],
290 clr[4], clr[5], clr[6], clr[7],
291 clr[8], clr[9], styleflags);
293 Pattern10Map::iterator i;
294 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
297 // TODO flush pattern cache if it gets too large
299 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
300 width, height, clr, stp, styleflags);
301 vm_pattern_cache[key] = p;
306 Cairo::RefPtr<Cairo::Pattern>
307 FastMeter::request_vertical_background(
308 int width, int height, int *bgc, bool shade)
310 if (height < min_pattern_metric_size)
311 height = min_pattern_metric_size;
312 if (height > max_pattern_metric_size)
313 height = max_pattern_metric_size;
315 const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
316 PatternBgMap::iterator i;
317 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
320 // TODO flush pattern cache if it gets too large
322 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
323 width, height, bgc, shade);
324 vb_pattern_cache[key] = p;
331 FastMeter::set_hold_count (long val)
345 FastMeter::on_size_request (GtkRequisition* req)
347 req->height = request_height;
348 req->height = max(req->height, min_pattern_metric_size);
349 req->height = min(req->height, max_pattern_metric_size);
352 req->width = request_width;
356 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
358 if (alloc.get_width() != request_width) {
359 alloc.set_width (request_width);
362 int h = alloc.get_height();
363 h = max (h, min_pattern_metric_size + 2);
364 h = min (h, max_pattern_metric_size + 2);
366 if (h != alloc.get_height()) {
367 alloc.set_height (h);
370 if (pixheight != h) {
371 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
372 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
374 pixwidth = request_width - 2;
377 DrawingArea::on_size_allocate (alloc);
381 FastMeter::on_expose_event (GdkEventExpose* ev)
383 return vertical_expose (ev);
387 FastMeter::vertical_expose (GdkEventExpose* ev)
389 Glib::RefPtr<Gdk::Window> win = get_window ();
391 GdkRectangle intersection;
392 GdkRectangle background;
394 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
396 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
399 cairo_set_source_rgb (cr, 0, 0, 0); // black
400 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
403 top_of_meter = (gint) floor (pixheight * current_level);
405 /* reset the height & origin of the rect that needs to show the pixbuf
408 pixrect.height = top_of_meter;
409 pixrect.y = 1 + pixheight - top_of_meter;
413 background.width = pixrect.width;
414 background.height = pixheight - top_of_meter;
416 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
417 cairo_set_source (cr, bgpattern->cobj());
418 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
422 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
423 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
424 cairo_set_source (cr, fgpattern->cobj());
425 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
432 last_peak_rect.x = 1;
433 last_peak_rect.width = pixwidth;
434 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
436 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
438 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
441 cairo_set_source (cr, fgpattern->cobj());
442 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
444 if (bright_hold && !no_rgba_overlay) {
445 cairo_fill_preserve (cr);
446 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
451 last_peak_rect.width = 0;
452 last_peak_rect.height = 0;
461 FastMeter::set (float lvl, float peak)
463 float old_level = current_level;
464 float old_peak = current_peak;
467 if (lvl >= current_peak) {
469 hold_state = hold_cnt;
472 if (hold_state > 0) {
473 if (--hold_state == 0) {
486 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
490 Glib::RefPtr<Gdk::Window> win;
492 if ((win = get_window()) == 0) {
497 queue_vertical_redraw (win, old_level);
501 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
505 gint new_top = (gint) floor (pixheight * current_level);
508 rect.width = pixwidth;
509 rect.height = new_top;
510 rect.y = 1 + pixheight - new_top;
512 if (current_level > old_level) {
513 /* colored/pixbuf got larger, just draw the new section */
514 /* rect.y stays where it is because of X coordinates */
515 /* height of invalidated area is between new.y (smaller) and old.y
517 X coordinates just make my brain hurt.
519 rect.height = pixrect.y - rect.y;
521 /* it got smaller, compute the difference */
522 /* rect.y becomes old.y (the smaller value) */
524 /* rect.height is the old.y (smaller) minus the new.y (larger)
526 rect.height = pixrect.height - rect.height;
529 GdkRegion* region = 0;
532 if (rect.height != 0) {
534 /* ok, first region to draw ... */
536 region = gdk_region_rectangle (&rect);
540 /* redraw the last place where the last peak hold bar was;
541 the next expose will draw the new one whether its part of
542 expose region or not.
545 if (last_peak_rect.width * last_peak_rect.height != 0) {
547 region = gdk_region_new ();
550 gdk_region_union_with_rect (region, &last_peak_rect);
553 if (hold_state && current_peak > 0) {
555 region = gdk_region_new ();
559 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
561 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
563 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
565 rect.width = pixwidth;
566 gdk_region_union_with_rect (region, &rect);
570 gdk_window_invalidate_region (win->gobj(), region, true);
573 gdk_region_destroy(region);
579 FastMeter::set_highlight (bool onoff)
581 if (highlight == onoff) {
585 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);