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