df3f09735e8be6c002e7dc953a398debe4f95396
[ardour.git] / libs / gtkmm2ext / fastmeter.cc
1 /*
2     Copyright (C) 2003-2006 Paul Davis
3
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.
8
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.
13
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.
17
18     $Id$
19 */
20
21 #include <iostream>
22 #include <cmath>
23 #include <algorithm>
24 #include <cstring>
25
26 #include <stdlib.h>
27
28 #include <glibmm.h>
29 #include <gdkmm.h>
30 #include <gdkmm/rectangle.h>
31 #include <gtkmm2ext/fastmeter.h>
32 #include <gtkmm2ext/utils.h>
33
34 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
35 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
36
37 using namespace Gtk;
38 using namespace Glib;
39 using namespace Gtkmm2ext;
40 using namespace std;
41
42 int FastMeter::min_pattern_metric_size = 16;
43 int FastMeter::max_pattern_metric_size = 1024;
44 bool FastMeter::no_rgba_overlay = false;
45
46 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
47 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
48
49 FastMeter::Pattern10Map FastMeter::hm_pattern_cache;
50 FastMeter::PatternBgMap FastMeter::hb_pattern_cache;
51
52 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
53                 int clr0, int clr1, int clr2, int clr3,
54                 int clr4, int clr5, int clr6, int clr7,
55                 int clr8, int clr9,
56                 int bgc0, int bgc1,
57                 int bgh0, int bgh1,
58                 float stp0, float stp1,
59                 float stp2, float stp3,
60                 int styleflags
61                 )
62         : pixheight(0)
63         , pixwidth(0)
64         , _styleflags(styleflags)
65         , orientation(o)
66         , hold_cnt(hold)
67         , hold_state(0)
68         , bright_hold(false)
69         , current_level(0)
70         , current_peak(0)
71         , highlight(false)
72 {
73         last_peak_rect.width = 0;
74         last_peak_rect.height = 0;
75         last_peak_rect.x = 0;
76         last_peak_rect.y = 0;
77
78         no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
79
80         _clr[0] = clr0;
81         _clr[1] = clr1;
82         _clr[2] = clr2;
83         _clr[3] = clr3;
84         _clr[4] = clr4;
85         _clr[5] = clr5;
86         _clr[6] = clr6;
87         _clr[7] = clr7;
88         _clr[8] = clr8;
89         _clr[9] = clr9;
90
91         _bgc[0] = bgc0;
92         _bgc[1] = bgc1;
93
94         _bgh[0] = bgh0;
95         _bgh[1] = bgh1;
96
97         _stp[0] = stp0;
98         _stp[1] = stp1;
99         _stp[2] = stp2;
100         _stp[3] = stp3;
101
102         set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
103
104         pixrect.x = 1;
105         pixrect.y = 1;
106
107         if (!len) {
108                 len = 250;
109         }
110         if (orientation == Vertical) {
111                 pixheight = len;
112                 pixwidth = dimen;
113                 fgpattern = request_vertical_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
114                 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, _bgc, false);
115
116         } else {
117                 pixheight = dimen;
118                 pixwidth = len;
119                 fgpattern = request_horizontal_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
120                 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, _bgc, false);
121         }
122
123         pixrect.width = pixwidth;
124         pixrect.height = pixheight;
125
126         request_width = pixrect.width + 2;
127         request_height= pixrect.height + 2;
128
129         clear ();
130 }
131
132 FastMeter::~FastMeter ()
133 {
134 }
135
136 Cairo::RefPtr<Cairo::Pattern>
137 FastMeter::generate_meter_pattern (
138                 int width, int height, int *clr, float *stp, int styleflags, bool horiz)
139 {
140         guint8 r,g,b,a;
141         double knee;
142         const double soft =  3.0 / (double) height;
143         const double offs = -1.0 / (double) height;
144
145         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
146
147         /*
148           Cairo coordinate space goes downwards as y value goes up, so invert
149           knee-based positions by using (1.0 - y)
150         */
151
152         UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
153         cairo_pattern_add_color_stop_rgb (pat, 0.0,
154                                           r/255.0, g/255.0, b/255.0);
155
156         knee = offs + stp[3] / 115.0f; // -0dB
157
158         UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
159         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
160                                           r/255.0, g/255.0, b/255.0);
161
162         UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
163         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
164                                           r/255.0, g/255.0, b/255.0);
165
166         knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
167
168         UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
169         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
170                                           r/255.0, g/255.0, b/255.0);
171
172         UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
173         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
174                                           r/255.0, g/255.0, b/255.0);
175
176         knee = offs + stp[1] / 115.0f; // -9dB
177
178         UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
179         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
180                                           r/255.0, g/255.0, b/255.0);
181
182         UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
183         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
184                                           r/255.0, g/255.0, b/255.0);
185
186         knee = offs + stp[0] / 115.0f; // -18dB
187
188         UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
189         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
190                                           r/255.0, g/255.0, b/255.0);
191
192         UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
193         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
194                                           r/255.0, g/255.0, b/255.0);
195
196         UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
197         cairo_pattern_add_color_stop_rgb (pat, 1.0,
198                                           r/255.0, g/255.0, b/255.0);
199
200         if ((styleflags & 1) && !no_rgba_overlay) {
201                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
202                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0,   0.0, 0.0, 0.0, 0.15);
203                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
204                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1,   0.0, 0.0, 0.0, 0.25);
205
206                 cairo_surface_t* surface;
207                 cairo_t* tc = 0;
208                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
209                 tc = cairo_create (surface);
210                 cairo_set_source (tc, pat);
211                 cairo_rectangle (tc, 0, 0, width, height);
212                 cairo_fill (tc);
213                 cairo_pattern_destroy (pat);
214
215                 cairo_set_source (tc, shade_pattern);
216                 cairo_rectangle (tc, 0, 0, width, height);
217                 cairo_fill (tc);
218                 cairo_pattern_destroy (shade_pattern);
219
220                 if (styleflags & 2) { // LED stripes
221                         cairo_save (tc);
222                         cairo_set_line_width(tc, 1.0);
223                         cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
224                         //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
225                         for (float y=0.5; y < height; y+= 2.0) {
226                                 cairo_move_to(tc, 0, y);
227                                 cairo_line_to(tc, width, y);
228                                 cairo_stroke (tc);
229                         }
230                         cairo_restore (tc);
231                 }
232
233                 pat = cairo_pattern_create_for_surface (surface);
234                 cairo_destroy (tc);
235                 cairo_surface_destroy (surface);
236         }
237
238         if (horiz) {
239                 cairo_surface_t* surface;
240                 cairo_t* tc = 0;
241                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
242                 tc = cairo_create (surface);
243
244                 cairo_matrix_t m;
245                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
246                 cairo_matrix_translate (&m, -height, 0);
247                 cairo_pattern_set_matrix (pat, &m);
248                 cairo_set_source (tc, pat);
249                 cairo_rectangle (tc, 0, 0, height, width);
250                 cairo_fill (tc);
251                 cairo_pattern_destroy (pat);
252                 pat = cairo_pattern_create_for_surface (surface);
253                 cairo_destroy (tc);
254                 cairo_surface_destroy (surface);
255         }
256         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
257
258         return p;
259 }
260
261
262 Cairo::RefPtr<Cairo::Pattern>
263 FastMeter::generate_meter_background (
264                 int width, int height, int *clr, bool shade, bool horiz)
265 {
266         guint8 r0,g0,b0,r1,g1,b1,a;
267
268         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
269
270         UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
271         UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
272
273         cairo_pattern_add_color_stop_rgb (pat, 0.0,
274                                           r1/255.0, g1/255.0, b1/255.0);
275
276         cairo_pattern_add_color_stop_rgb (pat, 1.0,
277                                           r0/255.0, g0/255.0, b0/255.0);
278
279         if (shade && !no_rgba_overlay) {
280                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
281                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
282                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
283                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
284
285                 cairo_surface_t* surface;
286                 cairo_t* tc = 0;
287                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
288                 tc = cairo_create (surface);
289                 cairo_set_source (tc, pat);
290                 cairo_rectangle (tc, 0, 0, width, height);
291                 cairo_fill (tc);
292                 cairo_set_source (tc, shade_pattern);
293                 cairo_rectangle (tc, 0, 0, width, height);
294                 cairo_fill (tc);
295
296                 cairo_pattern_destroy (pat);
297                 cairo_pattern_destroy (shade_pattern);
298
299                 pat = cairo_pattern_create_for_surface (surface);
300
301                 cairo_destroy (tc);
302                 cairo_surface_destroy (surface);
303         }
304
305         if (horiz) {
306                 cairo_surface_t* surface;
307                 cairo_t* tc = 0;
308                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
309                 tc = cairo_create (surface);
310
311                 cairo_matrix_t m;
312                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
313                 cairo_matrix_translate (&m, -height, 0);
314                 cairo_pattern_set_matrix (pat, &m);
315                 cairo_set_source (tc, pat);
316                 cairo_rectangle (tc, 0, 0, height, width);
317                 cairo_fill (tc);
318                 cairo_pattern_destroy (pat);
319                 pat = cairo_pattern_create_for_surface (surface);
320                 cairo_destroy (tc);
321                 cairo_surface_destroy (surface);
322         }
323
324         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
325
326         return p;
327 }
328
329 Cairo::RefPtr<Cairo::Pattern>
330 FastMeter::request_vertical_meter(
331                 int width, int height, int *clr, float *stp, int styleflags)
332 {
333         height = max(height, min_pattern_metric_size);
334         height = min(height, max_pattern_metric_size);
335
336         const Pattern10MapKey key (width, height,
337                         stp[0], stp[1], stp[2], stp[3],
338                         clr[0], clr[1], clr[2], clr[3],
339                         clr[4], clr[5], clr[6], clr[7],
340                         clr[8], clr[9], styleflags);
341
342         Pattern10Map::iterator i;
343         if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
344                 return i->second;
345         }
346         // TODO flush pattern cache if it gets too large
347
348         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
349                 width, height, clr, stp, styleflags, false);
350         vm_pattern_cache[key] = p;
351
352         return p;
353 }
354
355 Cairo::RefPtr<Cairo::Pattern>
356 FastMeter::request_vertical_background(
357                 int width, int height, int *bgc, bool shade)
358 {
359         height = max(height, min_pattern_metric_size);
360         height = min(height, max_pattern_metric_size);
361         height += 2;
362
363         const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
364         PatternBgMap::iterator i;
365         if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
366                 return i->second;
367         }
368         // TODO flush pattern cache if it gets too large
369
370         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
371                 width, height, bgc, shade, false);
372         vb_pattern_cache[key] = p;
373
374         return p;
375 }
376
377 Cairo::RefPtr<Cairo::Pattern>
378 FastMeter::request_horizontal_meter(
379                 int width, int height, int *clr, float *stp, int styleflags)
380 {
381         width = max(width, min_pattern_metric_size);
382         width = min(width, max_pattern_metric_size);
383
384         const Pattern10MapKey key (width, height,
385                         stp[0], stp[1], stp[2], stp[3],
386                         clr[0], clr[1], clr[2], clr[3],
387                         clr[4], clr[5], clr[6], clr[7],
388                         clr[8], clr[9], styleflags);
389
390         Pattern10Map::iterator i;
391         if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
392                 return i->second;
393         }
394         // TODO flush pattern cache if it gets too large
395
396         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
397                 height, width, clr, stp, styleflags, true);
398
399         hm_pattern_cache[key] = p;
400         return p;
401 }
402
403 Cairo::RefPtr<Cairo::Pattern>
404 FastMeter::request_horizontal_background(
405                 int width, int height, int *bgc, bool shade)
406 {
407         width = max(width, min_pattern_metric_size);
408         width = min(width, max_pattern_metric_size);
409         width += 2;
410
411         const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
412         PatternBgMap::iterator i;
413         if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
414                 return i->second;
415         }
416         // TODO flush pattern cache if it gets too large
417
418         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
419                 height, width, bgc, shade, true);
420
421         hb_pattern_cache[key] = p;
422
423         return p;
424 }
425
426
427
428 void
429 FastMeter::set_hold_count (long val)
430 {
431         if (val < 1) {
432                 val = 1;
433         }
434
435         hold_cnt = val;
436         hold_state = 0;
437         current_peak = 0;
438
439         queue_draw ();
440 }
441
442 void
443 FastMeter::on_size_request (GtkRequisition* req)
444 {
445         if (orientation == Vertical) {
446                 vertical_size_request (req);
447         } else {
448                 horizontal_size_request (req);
449         }
450 }
451
452 void
453 FastMeter::vertical_size_request (GtkRequisition* req)
454 {
455         req->height = request_height;
456         req->height = max(req->height, min_pattern_metric_size);
457         req->height = min(req->height, max_pattern_metric_size);
458         req->height += 2;
459
460         req->width  = request_width;
461 }
462
463 void
464 FastMeter::horizontal_size_request (GtkRequisition* req)
465 {
466         req->width = request_width;
467         req->width = max(req->width, min_pattern_metric_size);
468         req->width = min(req->width, max_pattern_metric_size);
469         req->width += 2;
470
471         req->height  = request_height;
472 }
473
474 void
475 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
476 {
477         if (orientation == Vertical) {
478                 vertical_size_allocate (alloc);
479         } else {
480                 horizontal_size_allocate (alloc);
481         }
482         queue_draw ();
483 }
484
485 void
486 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
487 {
488         if (alloc.get_width() != request_width) {
489                 alloc.set_width (request_width);
490         }
491
492         int h = alloc.get_height();
493         h = max (h, min_pattern_metric_size + 2);
494         h = min (h, max_pattern_metric_size + 2);
495
496         if (h != alloc.get_height()) {
497                 alloc.set_height (h);
498         }
499
500         if (pixheight != h) {
501                 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
502                 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
503                 pixheight = h - 2;
504                 pixwidth  = request_width - 2;
505         }
506
507         CairoWidget::on_size_allocate (alloc);
508 }
509
510 void
511 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
512 {
513         if (alloc.get_height() != request_height) {
514                 alloc.set_height (request_height);
515         }
516
517         int w = alloc.get_width();
518         w = max (w, min_pattern_metric_size + 2);
519         w = min (w, max_pattern_metric_size + 2);
520
521         if (w != alloc.get_width()) {
522                 alloc.set_width (w);
523         }
524
525         if (pixwidth != w) {
526                 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
527                 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, highlight);
528                 pixwidth = w - 2;
529                 pixheight  = request_height - 2;
530         }
531
532         CairoWidget::on_size_allocate (alloc);
533 }
534
535 void
536 FastMeter::render (cairo_t* cr, cairo_rectangle_t* area)
537 {
538         if (orientation == Vertical) {
539                 return vertical_expose (cr, area);
540         } else {
541                 return horizontal_expose (cr, area);
542         }
543 }
544
545 void
546 FastMeter::vertical_expose (cairo_t* cr, cairo_rectangle_t* area)
547 {
548         gint top_of_meter;
549         GdkRectangle intersection;
550         GdkRectangle background;
551         GdkRectangle eventarea;
552
553         cairo_set_source_rgb (cr, 0, 0, 0); // black
554         rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
555         cairo_stroke (cr);
556
557         top_of_meter = (gint) floor (pixheight * current_level);
558
559         /* reset the height & origin of the rect that needs to show the pixbuf
560          */
561
562         pixrect.height = top_of_meter;
563         pixrect.y = 1 + pixheight - top_of_meter;
564
565         background.x = 1;
566         background.y = 1;
567         background.width = pixrect.width;
568         background.height = pixheight - top_of_meter;
569
570         eventarea.x = area->x;
571         eventarea.y = area->y;
572         eventarea.width = area->width;
573         eventarea.height = area->height;
574
575         if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
576                 cairo_set_source (cr, bgpattern->cobj());
577                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
578                 cairo_fill (cr);
579         }
580
581         if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
582                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
583                 cairo_set_source (cr, fgpattern->cobj());
584                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
585                 cairo_fill (cr);
586         }
587
588         // draw peak bar
589
590         if (hold_state) {
591                 last_peak_rect.x = 1;
592                 last_peak_rect.width = pixwidth;
593                 last_peak_rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
594                 if (_styleflags & 2) { // LED stripes
595                         last_peak_rect.y = max(0, (last_peak_rect.y & (~1)));
596                 }
597                 if (bright_hold || (_styleflags & 2)) {
598                         last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y - 1 ));
599                 } else {
600                         last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y - 1 ));
601                 }
602
603                 cairo_set_source (cr, fgpattern->cobj());
604                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
605
606                 if (bright_hold && !no_rgba_overlay) {
607                         cairo_fill_preserve (cr);
608                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
609                 }
610                 cairo_fill (cr);
611
612         } else {
613                 last_peak_rect.width = 0;
614                 last_peak_rect.height = 0;
615         }
616 }
617
618 void
619 FastMeter::horizontal_expose (cairo_t* cr, cairo_rectangle_t* area)
620 {
621         gint right_of_meter;
622         GdkRectangle intersection;
623         GdkRectangle background;
624         GdkRectangle eventarea;
625
626         cairo_set_source_rgb (cr, 0, 0, 0); // black
627         rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
628         cairo_stroke (cr);
629
630         right_of_meter = (gint) floor (pixwidth * current_level);
631
632         /* reset the height & origin of the rect that needs to show the pixbuf
633          */
634
635         pixrect.width = right_of_meter;
636
637         background.x = 1 + right_of_meter;
638         background.y = 1;
639         background.width = pixwidth - right_of_meter;
640         background.height = pixheight;
641
642         eventarea.x = area->x;
643         eventarea.y = area->y;
644         eventarea.width = area->width;
645         eventarea.height = area->height;
646
647         if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
648                 cairo_set_source (cr, bgpattern->cobj());
649                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
650                 cairo_fill (cr);
651         }
652
653         if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
654                 cairo_set_source (cr, fgpattern->cobj());
655                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
656                 cairo_fill (cr);
657         }
658
659         // draw peak bar
660
661         if (hold_state) {
662                 last_peak_rect.y = 1;
663                 last_peak_rect.height = pixheight;
664                 const int xpos = floor (pixwidth * current_peak);
665                 if (bright_hold || (_styleflags & 2)) {
666                         last_peak_rect.width = min(3, xpos );
667                 } else {
668                         last_peak_rect.width = min(2, xpos );
669                 }
670                 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
671
672                 cairo_set_source (cr, fgpattern->cobj());
673                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
674
675                 if (bright_hold && !no_rgba_overlay) {
676                         cairo_fill_preserve (cr);
677                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
678                 }
679                 cairo_fill (cr);
680
681         } else {
682                 last_peak_rect.width = 0;
683                 last_peak_rect.height = 0;
684         }
685 }
686
687 void
688 FastMeter::set (float lvl, float peak)
689 {
690         float old_level = current_level;
691         float old_peak = current_peak;
692
693         if (pixwidth <= 0 || pixheight <=0) return;
694
695         if (peak == -1) {
696                 if (lvl >= current_peak) {
697                         current_peak = lvl;
698                         hold_state = hold_cnt;
699                 }
700
701                 if (hold_state > 0) {
702                         if (--hold_state == 0) {
703                                 current_peak = lvl;
704                         }
705                 }
706                 bright_hold = false;
707         } else {
708                 current_peak = peak;
709                 hold_state = 1;
710                 bright_hold = true;
711         }
712
713         current_level = lvl;
714
715         if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
716                 return;
717         }
718
719         Glib::RefPtr<Gdk::Window> win;
720
721         if ((win = get_window()) == 0) {
722                 queue_draw ();
723                 return;
724         }
725
726         if (orientation == Vertical) {
727                 queue_vertical_redraw (win, old_level);
728         } else {
729                 queue_horizontal_redraw (win, old_level);
730         }
731 }
732
733 void
734 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
735 {
736         GdkRectangle rect;
737
738         gint new_top = (gint) floor (pixheight * current_level);
739
740         rect.x = 1;
741         rect.width = pixwidth;
742         rect.height = new_top;
743         rect.y = 1 + pixheight - new_top;
744
745         if (current_level > old_level) {
746                 /* colored/pixbuf got larger, just draw the new section */
747                 /* rect.y stays where it is because of X coordinates */
748                 /* height of invalidated area is between new.y (smaller) and old.y
749                    (larger).
750                    X coordinates just make my brain hurt.
751                 */
752                 rect.height = pixrect.y - rect.y;
753         } else {
754                 /* it got smaller, compute the difference */
755                 /* rect.y becomes old.y (the smaller value) */
756                 rect.y = pixrect.y;
757                 /* rect.height is the old.y (smaller) minus the new.y (larger)
758                 */
759                 rect.height = pixrect.height - rect.height;
760         }
761
762         GdkRegion* region = 0;
763         bool queue = false;
764
765         if (rect.height != 0) {
766
767                 /* ok, first region to draw ... */
768
769                 region = gdk_region_rectangle (&rect);
770                 queue = true;
771         }
772
773         /* redraw the last place where the last peak hold bar was;
774            the next expose will draw the new one whether its part of
775            expose region or not.
776         */
777
778         if (last_peak_rect.width * last_peak_rect.height != 0) {
779                 if (!queue) {
780                         region = gdk_region_new ();
781                         queue = true;
782                 }
783                 gdk_region_union_with_rect (region, &last_peak_rect);
784         }
785
786         if (hold_state && current_peak > 0) {
787                 if (!queue) {
788                         region = gdk_region_new ();
789                         queue = true;
790                 }
791                 rect.x = 1;
792                 rect.y = max(1, 1 + pixheight - (int) floor (pixheight * current_peak));
793                 if (_styleflags & 2) { // LED stripes
794                         rect.y = max(0, (rect.y & (~1)));
795                 }
796                 if (bright_hold || (_styleflags & 2)) {
797                         rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
798                 } else {
799                         rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
800                 }
801                 rect.width = pixwidth;
802                 gdk_region_union_with_rect (region, &rect);
803         }
804
805         if (queue) {
806                 gdk_window_invalidate_region (win->gobj(), region, true);
807         }
808         if (region) {
809                 gdk_region_destroy(region);
810                 region = 0;
811         }
812 }
813
814 void
815 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
816 {
817         GdkRectangle rect;
818
819         gint new_right = (gint) floor (pixwidth * current_level);
820
821         rect.height = pixheight;
822         rect.y = 1;
823
824         if (current_level > old_level) {
825                 rect.x = 1 + pixrect.width;
826                 /* colored/pixbuf got larger, just draw the new section */
827                 rect.width = new_right - pixrect.width;
828         } else {
829                 /* it got smaller, compute the difference */
830                 rect.x = 1 + new_right;
831                 /* rect.height is the old.x (smaller) minus the new.x (larger) */
832                 rect.width = pixrect.width - new_right;
833         }
834
835         GdkRegion* region = 0;
836         bool queue = false;
837
838         if (rect.height != 0) {
839
840                 /* ok, first region to draw ... */
841
842                 region = gdk_region_rectangle (&rect);
843                 queue = true;
844         }
845
846         /* redraw the last place where the last peak hold bar was;
847            the next expose will draw the new one whether its part of
848            expose region or not.
849         */
850
851         if (last_peak_rect.width * last_peak_rect.height != 0) {
852                 if (!queue) {
853                         region = gdk_region_new ();
854                         queue = true;
855                 }
856                 gdk_region_union_with_rect (region, &last_peak_rect);
857         }
858
859         if (hold_state && current_peak > 0) {
860                 if (!queue) {
861                         region = gdk_region_new ();
862                         queue = true;
863                 }
864                 rect.y = 1;
865                 rect.height = pixheight;
866                 const int xpos = floor (pixwidth * current_peak);
867                 if (bright_hold || (_styleflags & 2)) {
868                         rect.width = min(3, xpos);
869                 } else {
870                         rect.width = min(2, xpos);
871                 }
872                 rect.x = 1 + max(0, xpos - rect.width);
873                 gdk_region_union_with_rect (region, &rect);
874         }
875
876         if (queue) {
877                 gdk_window_invalidate_region (win->gobj(), region, true);
878         }
879         if (region) {
880                 gdk_region_destroy(region);
881                 region = 0;
882         }
883 }
884
885 void
886 FastMeter::set_highlight (bool onoff)
887 {
888         if (highlight == onoff) {
889                 return;
890         }
891         highlight = onoff;
892         if (orientation == Vertical) {
893                 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
894         } else {
895                 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
896         }
897         queue_draw ();
898 }
899
900 void
901 FastMeter::clear ()
902 {
903         current_level = 0;
904         current_peak = 0;
905         hold_state = 0;
906         queue_draw ();
907 }