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);
359 FastMeter::on_expose_event (GdkEventExpose* ev)
361 return vertical_expose (ev);
365 FastMeter::vertical_expose (GdkEventExpose* ev)
367 Glib::RefPtr<Gdk::Window> win = get_window ();
369 GdkRectangle intersection;
370 GdkRectangle background;
372 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
375 cairo_set_source_rgb (cr, 0, 0, 0); // black
376 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
382 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
385 top_of_meter = (gint) floor (pixheight * current_level);
387 /* reset the height & origin of the rect that needs to show the pixbuf
390 pixrect.height = top_of_meter;
391 pixrect.y = 1 + pixheight - top_of_meter;
395 background.width = pixrect.width;
396 background.height = pixheight - top_of_meter;
398 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
399 cairo_set_source (cr, bgpattern->cobj());
400 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
404 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
405 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
406 cairo_set_source (cr, fgpattern->cobj());
407 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
414 last_peak_rect.x = 1;
415 last_peak_rect.width = pixwidth;
416 last_peak_rect.y = 1 + pixheight - (gint) floor (pixheight * current_peak);
417 last_peak_rect.height = min(2, pixheight - last_peak_rect.y);
419 cairo_set_source (cr, fgpattern->cobj());
420 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
424 last_peak_rect.width = 0;
425 last_peak_rect.height = 0;
434 FastMeter::set (float lvl)
436 float old_level = current_level;
437 float old_peak = current_peak;
441 if (lvl > current_peak) {
443 hold_state = hold_cnt;
446 if (hold_state > 0) {
447 if (--hold_state == 0) {
452 if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
457 Glib::RefPtr<Gdk::Window> win;
459 if ((win = get_window()) == 0) {
464 queue_vertical_redraw (win, old_level);
468 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
472 gint new_top = (gint) floor (pixheight * current_level);
475 rect.width = pixwidth;
476 rect.height = new_top;
477 rect.y = 1 + pixheight - new_top;
479 if (current_level > old_level) {
480 /* colored/pixbuf got larger, just draw the new section */
481 /* rect.y stays where it is because of X coordinates */
482 /* height of invalidated area is between new.y (smaller) and old.y
484 X coordinates just make my brain hurt.
486 rect.height = pixrect.y - rect.y;
488 /* it got smaller, compute the difference */
489 /* rect.y becomes old.y (the smaller value) */
491 /* rect.height is the old.y (smaller) minus the new.y (larger)
493 rect.height = pixrect.height - rect.height;
496 GdkRegion* region = 0;
499 if (rect.height != 0) {
501 /* ok, first region to draw ... */
503 region = gdk_region_rectangle (&rect);
507 /* redraw the last place where the last peak hold bar was;
508 the next expose will draw the new one whether its part of
509 expose region or not.
512 if (last_peak_rect.width * last_peak_rect.height != 0) {
514 region = gdk_region_new ();
517 gdk_region_union_with_rect (region, &last_peak_rect);
521 gdk_window_invalidate_region (win->gobj(), region, true);
524 gdk_region_destroy(region);
530 FastMeter::set_highlight (bool onoff)
532 if (highlight == onoff) {
536 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);
547 set_highlight(false);