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
63 last_peak_rect.width = 0;
64 last_peak_rect.height = 0;
67 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
91 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
99 fgpattern = request_vertical_meter(dimen, len, _clr, _stp, true);
100 bgpattern = request_vertical_background (dimen, len, _bgc, false);
104 pixrect.width = pixwidth;
105 pixrect.height = pixheight;
107 request_width = pixrect.width + 2;
108 request_height= pixrect.height + 2;
113 FastMeter::~FastMeter ()
117 Cairo::RefPtr<Cairo::Pattern>
118 FastMeter::generate_meter_pattern (
119 int width, int height, int *clr, float *stp, bool shade)
123 double soft = 1.5 / (double) height;
125 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
128 Cairo coordinate space goes downwards as y value goes up, so invert
129 knee-based positions by using (1.0 - y)
132 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
133 cairo_pattern_add_color_stop_rgb (pat, 0.0,
134 r/255.0, g/255.0, b/255.0);
136 knee = ((float)height * stp[3] / 115.0f); // -0dB
138 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
139 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
140 r/255.0, g/255.0, b/255.0);
142 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
143 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
144 r/255.0, g/255.0, b/255.0);
146 knee = ((float)height * stp[2]/ 115.0f); // -3dB || -2dB
148 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
149 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
150 r/255.0, g/255.0, b/255.0);
152 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
153 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
154 r/255.0, g/255.0, b/255.0);
156 knee = ((float)height * stp[1] / 115.0f); // -9dB
158 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
159 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
160 r/255.0, g/255.0, b/255.0);
162 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
163 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
164 r/255.0, g/255.0, b/255.0);
166 knee = ((float)height * stp[0] / 115.0f); // -18dB
168 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
169 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
170 r/255.0, g/255.0, b/255.0);
172 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
173 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
174 r/255.0, g/255.0, b/255.0);
176 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
177 cairo_pattern_add_color_stop_rgb (pat, 1.0,
178 r/255.0, g/255.0, b/255.0);
180 if (shade && !no_rgba_overlay) {
181 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
182 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1.0, 1.0, 1.0, 0.2);
183 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.3);
185 cairo_surface_t* surface;
187 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
188 tc = cairo_create (surface);
189 cairo_set_source (tc, pat);
190 cairo_rectangle (tc, 0, 0, width, height);
192 cairo_set_source (tc, shade_pattern);
193 cairo_rectangle (tc, 0, 0, width, height);
196 cairo_pattern_destroy (pat);
197 cairo_pattern_destroy (shade_pattern);
199 pat = cairo_pattern_create_for_surface (surface);
202 cairo_surface_destroy (surface);
205 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
211 Cairo::RefPtr<Cairo::Pattern>
212 FastMeter::generate_meter_background (
213 int width, int height, int *clr, bool shade)
215 guint8 r0,g0,b0,r1,g1,b1,a;
217 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
219 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
220 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
222 cairo_pattern_add_color_stop_rgb (pat, 0.0,
223 r1/255.0, g1/255.0, b1/255.0);
225 cairo_pattern_add_color_stop_rgb (pat, 1.0,
226 r0/255.0, g0/255.0, b0/255.0);
228 if (shade && !no_rgba_overlay) {
229 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
230 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
231 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
232 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
234 cairo_surface_t* surface;
236 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
237 tc = cairo_create (surface);
238 cairo_set_source (tc, pat);
239 cairo_rectangle (tc, 0, 0, width, height);
241 cairo_set_source (tc, shade_pattern);
242 cairo_rectangle (tc, 0, 0, width, height);
245 cairo_pattern_destroy (pat);
246 cairo_pattern_destroy (shade_pattern);
248 pat = cairo_pattern_create_for_surface (surface);
251 cairo_surface_destroy (surface);
254 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
259 Cairo::RefPtr<Cairo::Pattern>
260 FastMeter::request_vertical_meter(
261 int width, int height, int *clr, float *stp, bool shade)
263 if (height < min_pattern_metric_size)
264 height = min_pattern_metric_size;
265 if (height > max_pattern_metric_size)
266 height = max_pattern_metric_size;
268 const Pattern10MapKey key (width, height,
269 stp[0], stp[1], stp[2], stp[3],
270 clr[0], clr[1], clr[2], clr[3],
271 clr[4], clr[5], clr[6], clr[7],
274 Pattern10Map::iterator i;
275 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
278 // TODO flush pattern cache if it gets too large
280 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
281 width, height, clr, stp, shade);
282 vm_pattern_cache[key] = p;
287 Cairo::RefPtr<Cairo::Pattern>
288 FastMeter::request_vertical_background(
289 int width, int height, int *bgc, bool shade)
291 if (height < min_pattern_metric_size)
292 height = min_pattern_metric_size;
293 if (height > max_pattern_metric_size)
294 height = max_pattern_metric_size;
296 const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
297 PatternBgMap::iterator i;
298 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
301 // TODO flush pattern cache if it gets too large
303 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
304 width, height, bgc, shade);
305 vb_pattern_cache[key] = p;
312 FastMeter::set_hold_count (long val)
326 FastMeter::on_size_request (GtkRequisition* req)
328 req->height = request_height;
329 req->height = max(req->height, min_pattern_metric_size);
330 req->height = min(req->height, max_pattern_metric_size);
333 req->width = request_width;
337 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
339 if (alloc.get_width() != request_width) {
340 alloc.set_width (request_width);
343 int h = alloc.get_height();
344 h = max (h, min_pattern_metric_size + 2);
345 h = min (h, max_pattern_metric_size + 2);
347 if (h != alloc.get_height()) {
348 alloc.set_height (h);
351 if (pixheight != h) {
352 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, true);
353 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
355 pixwidth = request_width - 2;
358 DrawingArea::on_size_allocate (alloc);
362 FastMeter::on_expose_event (GdkEventExpose* ev)
364 return vertical_expose (ev);
368 FastMeter::vertical_expose (GdkEventExpose* ev)
370 Glib::RefPtr<Gdk::Window> win = get_window ();
372 GdkRectangle intersection;
373 GdkRectangle background;
375 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
377 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
380 cairo_set_source_rgb (cr, 0, 0, 0); // black
381 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
384 top_of_meter = (gint) floor (pixheight * current_level);
386 /* reset the height & origin of the rect that needs to show the pixbuf
389 pixrect.height = top_of_meter;
390 pixrect.y = 1 + pixheight - top_of_meter;
394 background.width = pixrect.width;
395 background.height = pixheight - top_of_meter;
397 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
398 cairo_set_source (cr, bgpattern->cobj());
399 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
403 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
404 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
405 cairo_set_source (cr, fgpattern->cobj());
406 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
413 last_peak_rect.x = 1;
414 last_peak_rect.width = pixwidth;
415 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
417 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
419 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
422 cairo_set_source (cr, fgpattern->cobj());
423 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
425 if (bright_hold && !no_rgba_overlay) {
426 cairo_fill_preserve (cr);
427 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
432 last_peak_rect.width = 0;
433 last_peak_rect.height = 0;
442 FastMeter::set (float lvl, float peak)
444 float old_level = current_level;
445 float old_peak = current_peak;
448 if (lvl >= current_peak) {
450 hold_state = hold_cnt;
453 if (hold_state > 0) {
454 if (--hold_state == 0) {
467 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
471 Glib::RefPtr<Gdk::Window> win;
473 if ((win = get_window()) == 0) {
478 queue_vertical_redraw (win, old_level);
482 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
486 gint new_top = (gint) floor (pixheight * current_level);
489 rect.width = pixwidth;
490 rect.height = new_top;
491 rect.y = 1 + pixheight - new_top;
493 if (current_level > old_level) {
494 /* colored/pixbuf got larger, just draw the new section */
495 /* rect.y stays where it is because of X coordinates */
496 /* height of invalidated area is between new.y (smaller) and old.y
498 X coordinates just make my brain hurt.
500 rect.height = pixrect.y - rect.y;
502 /* it got smaller, compute the difference */
503 /* rect.y becomes old.y (the smaller value) */
505 /* rect.height is the old.y (smaller) minus the new.y (larger)
507 rect.height = pixrect.height - rect.height;
510 GdkRegion* region = 0;
513 if (rect.height != 0) {
515 /* ok, first region to draw ... */
517 region = gdk_region_rectangle (&rect);
521 /* redraw the last place where the last peak hold bar was;
522 the next expose will draw the new one whether its part of
523 expose region or not.
526 if (last_peak_rect.width * last_peak_rect.height != 0) {
528 region = gdk_region_new ();
531 gdk_region_union_with_rect (region, &last_peak_rect);
534 if (hold_state && current_peak > 0) {
536 region = gdk_region_new ();
540 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
542 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
544 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
546 rect.width = pixwidth;
547 gdk_region_union_with_rect (region, &rect);
551 gdk_window_invalidate_region (win->gobj(), region, true);
554 gdk_region_destroy(region);
560 FastMeter::set_highlight (bool onoff)
562 if (highlight == onoff) {
566 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);