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