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