265fc3f5673b88939a4b22ebb9936ba56175b931
[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 =  3.0 / (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                 cairo_pattern_destroy (pat);
209
210                 cairo_set_source (tc, shade_pattern);
211                 cairo_rectangle (tc, 0, 0, width, height);
212                 cairo_fill (tc);
213                 cairo_pattern_destroy (shade_pattern);
214
215                 if (styleflags & 2) { // LED stripes
216                         cairo_save (tc);
217                         cairo_set_line_width(tc, 1.0);
218                         cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
219                         //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
220                         for (float y=0.5; y < height; y+= 2.0) {
221                                 cairo_move_to(tc, 0, y);
222                                 cairo_line_to(tc, width, y);
223                                 cairo_stroke (tc);
224                         }
225                         cairo_restore (tc);
226                 }
227
228                 pat = cairo_pattern_create_for_surface (surface);
229                 cairo_destroy (tc);
230                 cairo_surface_destroy (surface);
231         }
232
233         if (horiz) {
234                 cairo_surface_t* surface;
235                 cairo_t* tc = 0;
236                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
237                 tc = cairo_create (surface);
238
239                 cairo_matrix_t m;
240                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
241                 cairo_matrix_translate (&m, -height, 0);
242                 cairo_pattern_set_matrix (pat, &m);
243                 cairo_set_source (tc, pat);
244                 cairo_rectangle (tc, 0, 0, height, width);
245                 cairo_fill (tc);
246                 cairo_pattern_destroy (pat);
247                 pat = cairo_pattern_create_for_surface (surface);
248                 cairo_destroy (tc);
249                 cairo_surface_destroy (surface);
250         }
251         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
252
253         return p;
254 }
255
256
257 Cairo::RefPtr<Cairo::Pattern>
258 FastMeter::generate_meter_background (
259                 int width, int height, int *clr, bool shade, bool horiz)
260 {
261         guint8 r0,g0,b0,r1,g1,b1,a;
262
263         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
264
265         UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
266         UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
267
268         cairo_pattern_add_color_stop_rgb (pat, 0.0,
269                                           r1/255.0, g1/255.0, b1/255.0);
270
271         cairo_pattern_add_color_stop_rgb (pat, 1.0,
272                                           r0/255.0, g0/255.0, b0/255.0);
273
274         if (shade && !no_rgba_overlay) {
275                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
276                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
277                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
278                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
279
280                 cairo_surface_t* surface;
281                 cairo_t* tc = 0;
282                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
283                 tc = cairo_create (surface);
284                 cairo_set_source (tc, pat);
285                 cairo_rectangle (tc, 0, 0, width, height);
286                 cairo_fill (tc);
287                 cairo_set_source (tc, shade_pattern);
288                 cairo_rectangle (tc, 0, 0, width, height);
289                 cairo_fill (tc);
290
291                 cairo_pattern_destroy (pat);
292                 cairo_pattern_destroy (shade_pattern);
293
294                 pat = cairo_pattern_create_for_surface (surface);
295
296                 cairo_destroy (tc);
297                 cairo_surface_destroy (surface);
298         }
299
300         if (horiz) {
301                 cairo_surface_t* surface;
302                 cairo_t* tc = 0;
303                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
304                 tc = cairo_create (surface);
305
306                 cairo_matrix_t m;
307                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
308                 cairo_matrix_translate (&m, -height, 0);
309                 cairo_pattern_set_matrix (pat, &m);
310                 cairo_set_source (tc, pat);
311                 cairo_rectangle (tc, 0, 0, height, width);
312                 cairo_fill (tc);
313                 cairo_pattern_destroy (pat);
314                 pat = cairo_pattern_create_for_surface (surface);
315                 cairo_destroy (tc);
316                 cairo_surface_destroy (surface);
317         }
318
319         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
320
321         return p;
322 }
323
324 Cairo::RefPtr<Cairo::Pattern>
325 FastMeter::request_vertical_meter(
326                 int width, int height, int *clr, float *stp, int styleflags)
327 {
328         height = max(height, min_pattern_metric_size);
329         height = min(height, max_pattern_metric_size);
330
331         const Pattern10MapKey key (width, height,
332                         stp[0], stp[1], stp[2], stp[3],
333                         clr[0], clr[1], clr[2], clr[3],
334                         clr[4], clr[5], clr[6], clr[7],
335                         clr[8], clr[9], styleflags);
336
337         Pattern10Map::iterator i;
338         if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
339                 return i->second;
340         }
341         // TODO flush pattern cache if it gets too large
342
343         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
344                 width, height, clr, stp, styleflags, false);
345         vm_pattern_cache[key] = p;
346
347         return p;
348 }
349
350 Cairo::RefPtr<Cairo::Pattern>
351 FastMeter::request_vertical_background(
352                 int width, int height, int *bgc, bool shade)
353 {
354         height = max(height, min_pattern_metric_size);
355         height = min(height, max_pattern_metric_size);
356         height += 2;
357
358         const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
359         PatternBgMap::iterator i;
360         if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
361                 return i->second;
362         }
363         // TODO flush pattern cache if it gets too large
364
365         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
366                 width, height, bgc, shade, false);
367         vb_pattern_cache[key] = p;
368
369         return p;
370 }
371
372 Cairo::RefPtr<Cairo::Pattern>
373 FastMeter::request_horizontal_meter(
374                 int width, int height, int *clr, float *stp, int styleflags)
375 {
376         width = max(width, min_pattern_metric_size);
377         width = min(width, max_pattern_metric_size);
378
379         const Pattern10MapKey key (width, height,
380                         stp[0], stp[1], stp[2], stp[3],
381                         clr[0], clr[1], clr[2], clr[3],
382                         clr[4], clr[5], clr[6], clr[7],
383                         clr[8], clr[9], styleflags);
384
385         Pattern10Map::iterator i;
386         if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
387                 return i->second;
388         }
389         // TODO flush pattern cache if it gets too large
390
391         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
392                 height, width, clr, stp, styleflags, true);
393
394         hm_pattern_cache[key] = p;
395         return p;
396 }
397
398 Cairo::RefPtr<Cairo::Pattern>
399 FastMeter::request_horizontal_background(
400                 int width, int height, int *bgc, bool shade)
401 {
402         width = max(width, min_pattern_metric_size);
403         width = min(width, max_pattern_metric_size);
404         width += 2;
405
406         const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
407         PatternBgMap::iterator i;
408         if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
409                 return i->second;
410         }
411         // TODO flush pattern cache if it gets too large
412
413         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
414                 height, width, bgc, shade, true);
415
416         hb_pattern_cache[key] = p;
417
418         return p;
419 }
420
421
422
423 void
424 FastMeter::set_hold_count (long val)
425 {
426         if (val < 1) {
427                 val = 1;
428         }
429
430         hold_cnt = val;
431         hold_state = 0;
432         current_peak = 0;
433
434         queue_draw ();
435 }
436
437 void
438 FastMeter::on_size_request (GtkRequisition* req)
439 {
440         if (orientation == Vertical) {
441                 vertical_size_request (req);
442         } else {
443                 horizontal_size_request (req);
444         }
445 }
446
447 void
448 FastMeter::vertical_size_request (GtkRequisition* req)
449 {
450         req->height = request_height;
451         req->height = max(req->height, min_pattern_metric_size);
452         req->height = min(req->height, max_pattern_metric_size);
453         req->height += 2;
454
455         req->width  = request_width;
456 }
457
458 void
459 FastMeter::horizontal_size_request (GtkRequisition* req)
460 {
461         req->width = request_width;
462         req->width = max(req->width, min_pattern_metric_size);
463         req->width = min(req->width, max_pattern_metric_size);
464         req->width += 2;
465
466         req->height  = request_height;
467 }
468
469 void
470 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
471 {
472         if (orientation == Vertical) {
473                 vertical_size_allocate (alloc);
474         } else {
475                 horizontal_size_allocate (alloc);
476         }
477         queue_draw ();
478 }
479
480 void
481 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
482 {
483         if (alloc.get_width() != request_width) {
484                 alloc.set_width (request_width);
485         }
486
487         int h = alloc.get_height();
488         h = max (h, min_pattern_metric_size + 2);
489         h = min (h, max_pattern_metric_size + 2);
490
491         if (h != alloc.get_height()) {
492                 alloc.set_height (h);
493         }
494
495         if (pixheight != h) {
496                 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
497                 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
498                 pixheight = h - 2;
499                 pixwidth  = request_width - 2;
500         }
501
502         DrawingArea::on_size_allocate (alloc);
503 }
504
505 void
506 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
507 {
508         if (alloc.get_height() != request_height) {
509                 alloc.set_height (request_height);
510         }
511
512         int w = alloc.get_width();
513         w = max (w, min_pattern_metric_size + 2);
514         w = min (w, max_pattern_metric_size + 2);
515
516         if (w != alloc.get_width()) {
517                 alloc.set_width (w);
518         }
519
520         if (pixwidth != w) {
521                 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
522                 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, highlight);
523                 pixwidth = w - 2;
524                 pixheight  = request_height - 2;
525         }
526
527         DrawingArea::on_size_allocate (alloc);
528 }
529
530 bool
531 FastMeter::on_expose_event (GdkEventExpose* ev)
532 {
533         if (orientation == Vertical) {
534                 return vertical_expose (ev);
535         } else {
536                 return horizontal_expose (ev);
537         }
538 }
539
540 bool
541 FastMeter::vertical_expose (GdkEventExpose* ev)
542 {
543         Glib::RefPtr<Gdk::Window> win = get_window ();
544         gint top_of_meter;
545         GdkRectangle intersection;
546         GdkRectangle background;
547
548         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
549
550         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
551         cairo_clip (cr);
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         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
571                 cairo_set_source (cr, bgpattern->cobj());
572                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
573                 cairo_fill (cr);
574         }
575
576         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
577                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
578                 cairo_set_source (cr, fgpattern->cobj());
579                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
580                 cairo_fill (cr);
581         }
582
583         // draw peak bar
584
585         if (hold_state) {
586                 last_peak_rect.x = 1;
587                 last_peak_rect.width = pixwidth;
588                 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
589                 if (bright_hold || (_styleflags & 2)) {
590                         last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
591                 } else {
592                         last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
593                 }
594
595                 cairo_set_source (cr, fgpattern->cobj());
596                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
597
598                 if (bright_hold && !no_rgba_overlay) {
599                         cairo_fill_preserve (cr);
600                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
601                 }
602                 cairo_fill (cr);
603
604         } else {
605                 last_peak_rect.width = 0;
606                 last_peak_rect.height = 0;
607         }
608
609         cairo_destroy (cr);
610
611         return TRUE;
612 }
613
614 bool
615 FastMeter::horizontal_expose (GdkEventExpose* ev)
616 {
617         Glib::RefPtr<Gdk::Window> win = get_window ();
618         gint right_of_meter;
619         GdkRectangle intersection;
620         GdkRectangle background;
621
622         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
623
624         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
625         cairo_clip (cr);
626
627         cairo_set_source_rgb (cr, 0, 0, 0); // black
628         rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
629         cairo_stroke (cr);
630
631         right_of_meter = (gint) floor (pixwidth * current_level);
632
633         /* reset the height & origin of the rect that needs to show the pixbuf
634          */
635
636         pixrect.width = right_of_meter;
637
638         background.x = 1 + right_of_meter;
639         background.y = 1;
640         background.width = pixwidth - right_of_meter;
641         background.height = pixheight;
642
643         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
644                 cairo_set_source (cr, bgpattern->cobj());
645                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
646                 cairo_fill (cr);
647         }
648
649         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
650                 cairo_set_source (cr, fgpattern->cobj());
651                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
652                 cairo_fill (cr);
653         }
654
655         // draw peak bar
656
657         if (hold_state) {
658                 last_peak_rect.y = 1;
659                 last_peak_rect.height = pixheight;
660                 const int xpos = floor (pixwidth * current_peak);
661                 if (bright_hold) {
662                         last_peak_rect.width = min(4, xpos );
663                 } else {
664                         last_peak_rect.width = min(2, xpos );
665                 }
666                 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
667
668                 cairo_set_source (cr, fgpattern->cobj());
669                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
670
671                 if (bright_hold && !no_rgba_overlay) {
672                         cairo_fill_preserve (cr);
673                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
674                 }
675                 cairo_fill (cr);
676
677         } else {
678                 last_peak_rect.width = 0;
679                 last_peak_rect.height = 0;
680         }
681
682         cairo_destroy (cr);
683
684         return TRUE;
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 (peak == -1) {
694                 if (lvl >= current_peak) {
695                         current_peak = lvl;
696                         hold_state = hold_cnt;
697                 }
698
699                 if (hold_state > 0) {
700                         if (--hold_state == 0) {
701                                 current_peak = lvl;
702                         }
703                 }
704                 bright_hold = false;
705         } else {
706                 current_peak = peak;
707                 hold_state = 1;
708                 bright_hold = true;
709         }
710
711         current_level = lvl;
712
713         if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
714                 return;
715         }
716
717         Glib::RefPtr<Gdk::Window> win;
718
719         if ((win = get_window()) == 0) {
720                 queue_draw ();
721                 return;
722         }
723
724         if (orientation == Vertical) {
725                 queue_vertical_redraw (win, old_level);
726         } else {
727                 queue_horizontal_redraw (win, old_level);
728         }
729 }
730
731 void
732 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
733 {
734         GdkRectangle rect;
735
736         gint new_top = (gint) floor (pixheight * current_level);
737
738         rect.x = 1;
739         rect.width = pixwidth;
740         rect.height = new_top;
741         rect.y = 1 + pixheight - new_top;
742
743         if (current_level > old_level) {
744                 /* colored/pixbuf got larger, just draw the new section */
745                 /* rect.y stays where it is because of X coordinates */
746                 /* height of invalidated area is between new.y (smaller) and old.y
747                    (larger).
748                    X coordinates just make my brain hurt.
749                 */
750                 rect.height = pixrect.y - rect.y;
751         } else {
752                 /* it got smaller, compute the difference */
753                 /* rect.y becomes old.y (the smaller value) */
754                 rect.y = pixrect.y;
755                 /* rect.height is the old.y (smaller) minus the new.y (larger)
756                 */
757                 rect.height = pixrect.height - rect.height;
758         }
759
760         GdkRegion* region = 0;
761         bool queue = false;
762
763         if (rect.height != 0) {
764
765                 /* ok, first region to draw ... */
766
767                 region = gdk_region_rectangle (&rect);
768                 queue = true;
769         }
770
771         /* redraw the last place where the last peak hold bar was;
772            the next expose will draw the new one whether its part of
773            expose region or not.
774         */
775
776         if (last_peak_rect.width * last_peak_rect.height != 0) {
777                 if (!queue) {
778                         region = gdk_region_new ();
779                         queue = true;
780                 }
781                 gdk_region_union_with_rect (region, &last_peak_rect);
782         }
783
784         if (hold_state && current_peak > 0) {
785                 if (!queue) {
786                         region = gdk_region_new ();
787                         queue = true;
788                 }
789                 rect.x = 1;
790                 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
791                 if (bright_hold) {
792                         rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
793                 } else {
794                         rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
795                 }
796                 rect.width = pixwidth;
797                 gdk_region_union_with_rect (region, &rect);
798         }
799
800         if (queue) {
801                 gdk_window_invalidate_region (win->gobj(), region, true);
802         }
803         if (region) {
804                 gdk_region_destroy(region);
805                 region = 0;
806         }
807 }
808
809 void
810 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
811 {
812         GdkRectangle rect;
813
814         gint new_right = (gint) floor (pixwidth * current_level);
815
816         rect.height = pixheight;
817         rect.y = 1;
818
819         if (current_level > old_level) {
820                 rect.x = 1 + pixrect.width;
821                 /* colored/pixbuf got larger, just draw the new section */
822                 rect.width = new_right - pixrect.width;
823         } else {
824                 /* it got smaller, compute the difference */
825                 rect.x = 1 + new_right;
826                 /* rect.height is the old.x (smaller) minus the new.x (larger) */
827                 rect.width = pixrect.width - new_right;
828         }
829
830         GdkRegion* region = 0;
831         bool queue = false;
832
833         if (rect.height != 0) {
834
835                 /* ok, first region to draw ... */
836
837                 region = gdk_region_rectangle (&rect);
838                 queue = true;
839         }
840
841         /* redraw the last place where the last peak hold bar was;
842            the next expose will draw the new one whether its part of
843            expose region or not.
844         */
845
846         if (last_peak_rect.width * last_peak_rect.height != 0) {
847                 if (!queue) {
848                         region = gdk_region_new ();
849                         queue = true;
850                 }
851                 gdk_region_union_with_rect (region, &last_peak_rect);
852         }
853
854         if (hold_state && current_peak > 0) {
855                 if (!queue) {
856                         region = gdk_region_new ();
857                         queue = true;
858                 }
859                 rect.y = 1;
860                 rect.height = pixheight;
861                 const int xpos = floor (pixwidth * current_peak);
862                 if (bright_hold) {
863                         rect.width = min(4, xpos);
864                 } else {
865                         rect.width = min(2, xpos);
866                 }
867                 rect.x = 1 + max(0, xpos - rect.width);
868                 gdk_region_union_with_rect (region, &rect);
869         }
870
871         if (queue) {
872                 gdk_window_invalidate_region (win->gobj(), region, true);
873         }
874         if (region) {
875                 gdk_region_destroy(region);
876                 region = 0;
877         }
878 }
879
880 void
881 FastMeter::set_highlight (bool onoff)
882 {
883         if (highlight == onoff) {
884                 return;
885         }
886         highlight = onoff;
887         if (orientation == Vertical) {
888                 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
889         } else {
890                 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
891         }
892         queue_draw ();
893 }
894
895 void
896 FastMeter::clear ()
897 {
898         current_level = 0;
899         current_peak = 0;
900         hold_state = 0;
901         queue_draw ();
902 }