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
61 last_peak_rect.width = 0;
62 last_peak_rect.height = 0;
88 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
96 fgpattern = request_vertical_meter(dimen, len, _clr, _stp, true);
97 bgpattern = request_vertical_background (dimen, len, _bgc, false);
101 pixrect.width = pixwidth;
102 pixrect.height = pixheight;
104 request_width = pixrect.width + 2;
105 request_height= pixrect.height + 2;
110 FastMeter::~FastMeter ()
114 Cairo::RefPtr<Cairo::Pattern>
115 FastMeter::generate_meter_pattern (
116 int width, int height, int *clr, float *stp, bool shade)
120 double soft = 1.5 / (double) height;
122 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
125 Cairo coordinate space goes downwards as y value goes up, so invert
126 knee-based positions by using (1.0 - y)
129 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
130 cairo_pattern_add_color_stop_rgb (pat, 0.0,
131 r/255.0, g/255.0, b/255.0);
133 knee = ((float)height * stp[3] / 115.0f); // -0dB
135 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
136 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
137 r/255.0, g/255.0, b/255.0);
139 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
140 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
141 r/255.0, g/255.0, b/255.0);
143 knee = ((float)height * stp[2]/ 115.0f); // -3dB || -2dB
145 UINT_TO_RGBA (clr[6], &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 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
150 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
151 r/255.0, g/255.0, b/255.0);
153 knee = ((float)height * stp[1] / 115.0f); // -9dB
155 UINT_TO_RGBA (clr[4], &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 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
160 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
161 r/255.0, g/255.0, b/255.0);
163 knee = ((float)height * stp[0] / 115.0f); // -18dB
165 UINT_TO_RGBA (clr[2], &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 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
170 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
171 r/255.0, g/255.0, b/255.0);
173 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
174 cairo_pattern_add_color_stop_rgb (pat, 1.0,
175 r/255.0, g/255.0, b/255.0);
178 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
179 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1.0, 1.0, 1.0, 0.2);
180 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.3);
182 cairo_surface_t* surface;
184 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
185 tc = cairo_create (surface);
186 cairo_set_source (tc, pat);
187 cairo_rectangle (tc, 0, 0, width, height);
189 cairo_set_source (tc, shade_pattern);
190 cairo_rectangle (tc, 0, 0, width, height);
193 cairo_pattern_destroy (pat);
194 cairo_pattern_destroy (shade_pattern);
196 pat = cairo_pattern_create_for_surface (surface);
199 cairo_surface_destroy (surface);
202 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
208 Cairo::RefPtr<Cairo::Pattern>
209 FastMeter::generate_meter_background (
210 int width, int height, int *clr, bool shade)
212 guint8 r0,g0,b0,r1,g1,b1,a;
214 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
216 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
217 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
219 cairo_pattern_add_color_stop_rgb (pat, 0.0,
220 r1/255.0, g1/255.0, b1/255.0);
222 cairo_pattern_add_color_stop_rgb (pat, 1.0,
223 r0/255.0, g0/255.0, b0/255.0);
226 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
227 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
228 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
229 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
231 cairo_surface_t* surface;
233 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
234 tc = cairo_create (surface);
235 cairo_set_source (tc, pat);
236 cairo_rectangle (tc, 0, 0, width, height);
238 cairo_set_source (tc, shade_pattern);
239 cairo_rectangle (tc, 0, 0, width, height);
242 cairo_pattern_destroy (pat);
243 cairo_pattern_destroy (shade_pattern);
245 pat = cairo_pattern_create_for_surface (surface);
248 cairo_surface_destroy (surface);
251 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
256 Cairo::RefPtr<Cairo::Pattern>
257 FastMeter::request_vertical_meter(
258 int width, int height, int *clr, float *stp, bool shade)
260 if (height < min_pattern_metric_size)
261 height = min_pattern_metric_size;
262 if (height > max_pattern_metric_size)
263 height = max_pattern_metric_size;
265 const Pattern10MapKey key (width, height,
266 stp[0], stp[1], stp[2], stp[3],
267 clr[0], clr[1], clr[2], clr[3],
268 clr[4], clr[5], clr[6], clr[7],
271 Pattern10Map::iterator i;
272 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
275 // TODO flush pattern cache if it gets too large
277 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
278 width, height, clr, stp, shade);
279 vm_pattern_cache[key] = p;
284 Cairo::RefPtr<Cairo::Pattern>
285 FastMeter::request_vertical_background(
286 int width, int height, int *bgc, bool shade)
288 if (height < min_pattern_metric_size)
289 height = min_pattern_metric_size;
290 if (height > max_pattern_metric_size)
291 height = max_pattern_metric_size;
293 const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
294 PatternBgMap::iterator i;
295 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
298 // TODO flush pattern cache if it gets too large
300 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
301 width, height, bgc, shade);
302 vb_pattern_cache[key] = p;
309 FastMeter::set_hold_count (long val)
323 FastMeter::on_size_request (GtkRequisition* req)
325 req->height = request_height;
326 req->height = max(req->height, min_pattern_metric_size);
327 req->height = min(req->height, max_pattern_metric_size);
330 req->width = request_width;
334 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
336 if (alloc.get_width() != request_width) {
337 alloc.set_width (request_width);
340 int h = alloc.get_height();
341 h = max (h, min_pattern_metric_size + 2);
342 h = min (h, max_pattern_metric_size + 2);
344 if (h != alloc.get_height()) {
345 alloc.set_height (h);
348 if (pixheight != h) {
349 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, true);
350 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
352 pixwidth = request_width - 2;
355 DrawingArea::on_size_allocate (alloc);
360 FastMeter::on_expose_event (GdkEventExpose* ev)
362 return vertical_expose (ev);
366 FastMeter::vertical_expose (GdkEventExpose* ev)
368 Glib::RefPtr<Gdk::Window> win = get_window ();
370 GdkRectangle intersection;
371 GdkRectangle background;
373 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
376 cairo_set_source_rgb (cr, 0, 0, 0); // black
377 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 = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
418 last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
420 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
423 cairo_set_source (cr, fgpattern->cobj());
424 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
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) {
472 Glib::RefPtr<Gdk::Window> win;
474 if ((win = get_window()) == 0) {
479 queue_vertical_redraw (win, old_level);
483 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
487 gint new_top = (gint) floor (pixheight * current_level);
490 rect.width = pixwidth;
491 rect.height = new_top;
492 rect.y = 1 + pixheight - new_top;
494 if (current_level > old_level) {
495 /* colored/pixbuf got larger, just draw the new section */
496 /* rect.y stays where it is because of X coordinates */
497 /* height of invalidated area is between new.y (smaller) and old.y
499 X coordinates just make my brain hurt.
501 rect.height = pixrect.y - rect.y;
503 /* it got smaller, compute the difference */
504 /* rect.y becomes old.y (the smaller value) */
506 /* rect.height is the old.y (smaller) minus the new.y (larger)
508 rect.height = pixrect.height - rect.height;
511 GdkRegion* region = 0;
514 if (rect.height != 0) {
516 /* ok, first region to draw ... */
518 region = gdk_region_rectangle (&rect);
522 /* redraw the last place where the last peak hold bar was;
523 the next expose will draw the new one whether its part of
524 expose region or not.
527 if (last_peak_rect.width * last_peak_rect.height != 0) {
529 region = gdk_region_new ();
532 gdk_region_union_with_rect (region, &last_peak_rect);
535 if (hold_state && current_peak > 0) {
537 region = gdk_region_new ();
541 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
543 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
545 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
547 rect.width = pixwidth;
548 gdk_region_union_with_rect (region, &rect);
552 gdk_window_invalidate_region (win->gobj(), region, true);
555 gdk_region_destroy(region);
561 FastMeter::set_highlight (bool onoff)
563 if (highlight == onoff) {
567 bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);