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 double soft = 1.5 / (double) height;
128 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
131 Cairo coordinate space goes downwards as y value goes up, so invert
132 knee-based positions by using (1.0 - y)
135 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
136 cairo_pattern_add_color_stop_rgb (pat, 0.0,
137 r/255.0, g/255.0, b/255.0);
139 knee = ((float)height * stp[3] / 115.0f); // -0dB
141 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
142 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
143 r/255.0, g/255.0, b/255.0);
145 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
146 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
147 r/255.0, g/255.0, b/255.0);
149 knee = ((float)height * stp[2]/ 115.0f); // -3dB || -2dB
151 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
152 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
153 r/255.0, g/255.0, b/255.0);
155 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
156 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
157 r/255.0, g/255.0, b/255.0);
159 knee = ((float)height * stp[1] / 115.0f); // -9dB
161 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
162 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
163 r/255.0, g/255.0, b/255.0);
165 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
166 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
167 r/255.0, g/255.0, b/255.0);
169 knee = ((float)height * stp[0] / 115.0f); // -18dB
171 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
172 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
173 r/255.0, g/255.0, b/255.0);
175 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
176 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
177 r/255.0, g/255.0, b/255.0);
179 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
180 cairo_pattern_add_color_stop_rgb (pat, 1.0,
181 r/255.0, g/255.0, b/255.0);
183 if ((styleflags & 1) && !no_rgba_overlay) {
184 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
185 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
186 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
187 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
189 cairo_surface_t* surface;
191 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
192 tc = cairo_create (surface);
193 cairo_set_source (tc, pat);
194 cairo_rectangle (tc, 0, 0, width, height);
197 if (styleflags & 2) { // LED stripes
199 cairo_set_line_width(tc, 1.0);
200 cairo_set_source_rgba(tc, .0, .0, .0, .3);
201 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
202 for (float y=.5; y < height; y+= 2.0) {
203 cairo_move_to(tc, 0, y);
204 cairo_line_to(tc, width, y);
210 cairo_set_source (tc, shade_pattern);
211 cairo_rectangle (tc, 0, 0, width, height);
214 cairo_pattern_destroy (pat);
215 cairo_pattern_destroy (shade_pattern);
217 pat = cairo_pattern_create_for_surface (surface);
220 cairo_surface_destroy (surface);
223 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
229 Cairo::RefPtr<Cairo::Pattern>
230 FastMeter::generate_meter_background (
231 int width, int height, int *clr, bool shade)
233 guint8 r0,g0,b0,r1,g1,b1,a;
235 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
237 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
238 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
240 cairo_pattern_add_color_stop_rgb (pat, 0.0,
241 r1/255.0, g1/255.0, b1/255.0);
243 cairo_pattern_add_color_stop_rgb (pat, 1.0,
244 r0/255.0, g0/255.0, b0/255.0);
246 if (shade && !no_rgba_overlay) {
247 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
248 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
249 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
250 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
252 cairo_surface_t* surface;
254 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
255 tc = cairo_create (surface);
256 cairo_set_source (tc, pat);
257 cairo_rectangle (tc, 0, 0, width, height);
259 cairo_set_source (tc, shade_pattern);
260 cairo_rectangle (tc, 0, 0, width, height);
263 cairo_pattern_destroy (pat);
264 cairo_pattern_destroy (shade_pattern);
266 pat = cairo_pattern_create_for_surface (surface);
269 cairo_surface_destroy (surface);
272 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
277 Cairo::RefPtr<Cairo::Pattern>
278 FastMeter::request_vertical_meter(
279 int width, int height, int *clr, float *stp, int styleflags)
281 if (height < min_pattern_metric_size)
282 height = min_pattern_metric_size;
283 if (height > max_pattern_metric_size)
284 height = max_pattern_metric_size;
286 const Pattern10MapKey key (width, height,
287 stp[0], stp[1], stp[2], stp[3],
288 clr[0], clr[1], clr[2], clr[3],
289 clr[4], clr[5], clr[6], clr[7],
290 clr[8], clr[9], styleflags);
292 Pattern10Map::iterator i;
293 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
296 // TODO flush pattern cache if it gets too large
298 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
299 width, height, clr, stp, styleflags);
300 vm_pattern_cache[key] = p;
305 Cairo::RefPtr<Cairo::Pattern>
306 FastMeter::request_vertical_background(
307 int width, int height, int *bgc, bool shade)
309 if (height < min_pattern_metric_size)
310 height = min_pattern_metric_size;
311 if (height > max_pattern_metric_size)
312 height = max_pattern_metric_size;
314 const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
315 PatternBgMap::iterator i;
316 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
319 // TODO flush pattern cache if it gets too large
321 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
322 width, height, bgc, shade);
323 vb_pattern_cache[key] = p;
330 FastMeter::set_hold_count (long val)
344 FastMeter::on_size_request (GtkRequisition* req)
346 req->height = request_height;
347 req->height = max(req->height, min_pattern_metric_size);
348 req->height = min(req->height, max_pattern_metric_size);
351 req->width = request_width;
355 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
357 if (alloc.get_width() != request_width) {
358 alloc.set_width (request_width);
361 int h = alloc.get_height();
362 h = max (h, min_pattern_metric_size + 2);
363 h = min (h, max_pattern_metric_size + 2);
365 if (h != alloc.get_height()) {
366 alloc.set_height (h);
369 if (pixheight != h) {
370 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
371 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
373 pixwidth = request_width - 2;
376 DrawingArea::on_size_allocate (alloc);
380 FastMeter::on_expose_event (GdkEventExpose* ev)
382 return vertical_expose (ev);
386 FastMeter::vertical_expose (GdkEventExpose* ev)
388 Glib::RefPtr<Gdk::Window> win = get_window ();
390 GdkRectangle intersection;
391 GdkRectangle background;
393 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
395 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
398 cairo_set_source_rgb (cr, 0, 0, 0); // black
399 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
402 top_of_meter = (gint) floor (pixheight * current_level);
404 /* reset the height & origin of the rect that needs to show the pixbuf
407 pixrect.height = top_of_meter;
408 pixrect.y = 1 + pixheight - top_of_meter;
412 background.width = pixrect.width;
413 background.height = pixheight - top_of_meter;
415 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
416 cairo_set_source (cr, bgpattern->cobj());
417 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
421 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
422 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
423 cairo_set_source (cr, fgpattern->cobj());
424 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
431 last_peak_rect.x = 1;
432 last_peak_rect.width = pixwidth;
433 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
435 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
437 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
440 cairo_set_source (cr, fgpattern->cobj());
441 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
443 if (bright_hold && !no_rgba_overlay) {
444 cairo_fill_preserve (cr);
445 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
450 last_peak_rect.width = 0;
451 last_peak_rect.height = 0;
460 FastMeter::set (float lvl, float peak)
462 float old_level = current_level;
463 float old_peak = current_peak;
466 if (lvl >= current_peak) {
468 hold_state = hold_cnt;
471 if (hold_state > 0) {
472 if (--hold_state == 0) {
485 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
489 Glib::RefPtr<Gdk::Window> win;
491 if ((win = get_window()) == 0) {
496 queue_vertical_redraw (win, old_level);
500 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
504 gint new_top = (gint) floor (pixheight * current_level);
507 rect.width = pixwidth;
508 rect.height = new_top;
509 rect.y = 1 + pixheight - new_top;
511 if (current_level > old_level) {
512 /* colored/pixbuf got larger, just draw the new section */
513 /* rect.y stays where it is because of X coordinates */
514 /* height of invalidated area is between new.y (smaller) and old.y
516 X coordinates just make my brain hurt.
518 rect.height = pixrect.y - rect.y;
520 /* it got smaller, compute the difference */
521 /* rect.y becomes old.y (the smaller value) */
523 /* rect.height is the old.y (smaller) minus the new.y (larger)
525 rect.height = pixrect.height - rect.height;
528 GdkRegion* region = 0;
531 if (rect.height != 0) {
533 /* ok, first region to draw ... */
535 region = gdk_region_rectangle (&rect);
539 /* redraw the last place where the last peak hold bar was;
540 the next expose will draw the new one whether its part of
541 expose region or not.
544 if (last_peak_rect.width * last_peak_rect.height != 0) {
546 region = gdk_region_new ();
549 gdk_region_union_with_rect (region, &last_peak_rect);
552 if (hold_state && current_peak > 0) {
554 region = gdk_region_new ();
558 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
560 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
562 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
564 rect.width = pixwidth;
565 gdk_region_union_with_rect (region, &rect);
569 gdk_window_invalidate_region (win->gobj(), region, true);
572 gdk_region_destroy(region);
578 FastMeter::set_highlight (bool onoff)
580 if (highlight == onoff) {
584 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);