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.
26 #include <gdkmm/rectangle.h>
27 #include <gtkmm2ext/fastmeter.h>
28 #include <gtkmm2ext/utils.h>
30 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
31 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
35 using namespace Gtkmm2ext;
38 int FastMeter::min_pattern_metric_size = 16;
39 int FastMeter::max_pattern_metric_size = 1024;
41 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
42 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
44 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
45 int clr0, int clr1, int clr2, int clr3,
46 int clr4, int clr5, int clr6, int clr7,
50 float stp0, float stp1,
51 float stp2, float stp3
60 last_peak_rect.width = 0;
61 last_peak_rect.height = 0;
87 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
95 fgpattern = request_vertical_meter(dimen, len, _clr, _stp, true);
96 bgpattern = request_vertical_background (dimen, len, _bgc, false);
100 pixrect.width = pixwidth;
101 pixrect.height = pixheight;
103 request_width = pixrect.width + 2;
104 request_height= pixrect.height + 2;
109 FastMeter::~FastMeter ()
113 Cairo::RefPtr<Cairo::Pattern>
114 FastMeter::generate_meter_pattern (
115 int width, int height, int *clr, float *stp, bool shade)
119 double soft = 1.5 / (double) height;
121 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
124 Cairo coordinate space goes downwards as y value goes up, so invert
125 knee-based positions by using (1.0 - y)
128 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
129 cairo_pattern_add_color_stop_rgb (pat, 0.0,
130 r/255.0, g/255.0, b/255.0);
132 knee = ((float)height * stp[3] / 115.0f); // -0dB
134 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
135 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
136 r/255.0, g/255.0, b/255.0);
138 UINT_TO_RGBA (clr[7], &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 knee = ((float)height * stp[2]/ 115.0f); // -3dB || -2dB
144 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
145 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
146 r/255.0, g/255.0, b/255.0);
148 UINT_TO_RGBA (clr[5], &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 knee = ((float)height * stp[1] / 115.0f); // -9dB
154 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
155 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
156 r/255.0, g/255.0, b/255.0);
158 UINT_TO_RGBA (clr[3], &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 knee = ((float)height * stp[0] / 115.0f); // -18dB
164 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
165 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
166 r/255.0, g/255.0, b/255.0);
168 UINT_TO_RGBA (clr[1], &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[0], &r, &g, &b, &a); // bottom
173 cairo_pattern_add_color_stop_rgb (pat, 1.0,
174 r/255.0, g/255.0, b/255.0);
177 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
178 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1.0, 1.0, 1.0, 0.2);
179 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.3);
181 cairo_surface_t* surface;
183 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
184 tc = cairo_create (surface);
185 cairo_set_source (tc, pat);
186 cairo_rectangle (tc, 0, 0, width, height);
188 cairo_set_source (tc, shade_pattern);
189 cairo_rectangle (tc, 0, 0, width, height);
192 cairo_pattern_destroy (pat);
193 cairo_pattern_destroy (shade_pattern);
195 pat = cairo_pattern_create_for_surface (surface);
198 cairo_surface_destroy (surface);
201 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
207 Cairo::RefPtr<Cairo::Pattern>
208 FastMeter::generate_meter_background (
209 int width, int height, int *clr, bool shade)
211 guint8 r0,g0,b0,r1,g1,b1,a;
213 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
215 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
216 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
218 cairo_pattern_add_color_stop_rgb (pat, 0.0,
219 r1/255.0, g1/255.0, b1/255.0);
221 cairo_pattern_add_color_stop_rgb (pat, 1.0,
222 r0/255.0, g0/255.0, b0/255.0);
225 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
226 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
227 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
228 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
230 cairo_surface_t* surface;
232 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
233 tc = cairo_create (surface);
234 cairo_set_source (tc, pat);
235 cairo_rectangle (tc, 0, 0, width, height);
237 cairo_set_source (tc, shade_pattern);
238 cairo_rectangle (tc, 0, 0, width, height);
241 cairo_pattern_destroy (pat);
242 cairo_pattern_destroy (shade_pattern);
244 pat = cairo_pattern_create_for_surface (surface);
247 cairo_surface_destroy (surface);
250 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
255 Cairo::RefPtr<Cairo::Pattern>
256 FastMeter::request_vertical_meter(
257 int width, int height, int *clr, float *stp, bool shade)
259 if (height < min_pattern_metric_size)
260 height = min_pattern_metric_size;
261 if (height > max_pattern_metric_size)
262 height = max_pattern_metric_size;
264 const Pattern10MapKey key (width, height,
265 stp[0], stp[1], stp[2], stp[3],
266 clr[0], clr[1], clr[2], clr[3],
267 clr[4], clr[5], clr[6], clr[7],
270 Pattern10Map::iterator i;
271 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
274 // TODO flush pattern cache if it gets too large
276 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
277 width, height, clr, stp, shade);
278 vm_pattern_cache[key] = p;
283 Cairo::RefPtr<Cairo::Pattern>
284 FastMeter::request_vertical_background(
285 int width, int height, int *bgc, bool shade)
287 if (height < min_pattern_metric_size)
288 height = min_pattern_metric_size;
289 if (height > max_pattern_metric_size)
290 height = max_pattern_metric_size;
292 const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
293 PatternBgMap::iterator i;
294 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
297 // TODO flush pattern cache if it gets too large
299 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
300 width, height, bgc, shade);
301 vb_pattern_cache[key] = p;
308 FastMeter::set_hold_count (long val)
322 FastMeter::on_size_request (GtkRequisition* req)
324 req->height = request_height;
325 req->height = max(req->height, min_pattern_metric_size);
326 req->height = min(req->height, max_pattern_metric_size);
329 req->width = request_width;
333 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
335 if (alloc.get_width() != request_width) {
336 alloc.set_width (request_width);
339 int h = alloc.get_height();
340 h = max (h, min_pattern_metric_size + 2);
341 h = min (h, max_pattern_metric_size + 2);
343 if (h != alloc.get_height()) {
344 alloc.set_height (h);
347 if (pixheight != h) {
348 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, true);
349 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
351 pixwidth = request_width - 2;
354 DrawingArea::on_size_allocate (alloc);
358 FastMeter::on_expose_event (GdkEventExpose* ev)
360 return vertical_expose (ev);
364 FastMeter::vertical_expose (GdkEventExpose* ev)
366 Glib::RefPtr<Gdk::Window> win = get_window ();
368 GdkRectangle intersection;
369 GdkRectangle background;
371 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
373 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
376 cairo_set_source_rgb (cr, 0, 0, 0); // black
377 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
380 top_of_meter = (gint) floor (pixheight * current_level);
382 /* reset the height & origin of the rect that needs to show the pixbuf
385 pixrect.height = top_of_meter;
386 pixrect.y = 1 + pixheight - top_of_meter;
390 background.width = pixrect.width;
391 background.height = pixheight - top_of_meter;
393 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
394 cairo_set_source (cr, bgpattern->cobj());
395 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
399 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
400 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
401 cairo_set_source (cr, fgpattern->cobj());
402 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
409 last_peak_rect.x = 1;
410 last_peak_rect.width = pixwidth;
411 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
413 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
415 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
418 cairo_set_source (cr, fgpattern->cobj());
419 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
421 cairo_fill_preserve (cr);
422 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
427 last_peak_rect.width = 0;
428 last_peak_rect.height = 0;
437 FastMeter::set (float lvl, float peak)
439 float old_level = current_level;
440 float old_peak = current_peak;
443 if (lvl >= current_peak) {
445 hold_state = hold_cnt;
448 if (hold_state > 0) {
449 if (--hold_state == 0) {
462 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
466 Glib::RefPtr<Gdk::Window> win;
468 if ((win = get_window()) == 0) {
473 queue_vertical_redraw (win, old_level);
477 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
481 gint new_top = (gint) floor (pixheight * current_level);
484 rect.width = pixwidth;
485 rect.height = new_top;
486 rect.y = 1 + pixheight - new_top;
488 if (current_level > old_level) {
489 /* colored/pixbuf got larger, just draw the new section */
490 /* rect.y stays where it is because of X coordinates */
491 /* height of invalidated area is between new.y (smaller) and old.y
493 X coordinates just make my brain hurt.
495 rect.height = pixrect.y - rect.y;
497 /* it got smaller, compute the difference */
498 /* rect.y becomes old.y (the smaller value) */
500 /* rect.height is the old.y (smaller) minus the new.y (larger)
502 rect.height = pixrect.height - rect.height;
505 GdkRegion* region = 0;
508 if (rect.height != 0) {
510 /* ok, first region to draw ... */
512 region = gdk_region_rectangle (&rect);
516 /* redraw the last place where the last peak hold bar was;
517 the next expose will draw the new one whether its part of
518 expose region or not.
521 if (last_peak_rect.width * last_peak_rect.height != 0) {
523 region = gdk_region_new ();
526 gdk_region_union_with_rect (region, &last_peak_rect);
529 if (hold_state && current_peak > 0) {
531 region = gdk_region_new ();
535 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
537 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
539 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
541 rect.width = pixwidth;
542 gdk_region_union_with_rect (region, &rect);
546 gdk_window_invalidate_region (win->gobj(), region, true);
549 gdk_region_destroy(region);
555 FastMeter::set_highlight (bool onoff)
557 if (highlight == onoff) {
561 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);