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