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