fa8cba9f7b97457974a7aeaeec370bffcfdd2a2c
[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 intersection;
573         GdkRectangle background;
574         GdkRectangle eventarea;
575
576         //cairo_set_source_rgb (cr, 0, 0, 0); // black
577         //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
578         //cairo_stroke (cr);
579
580         top_of_meter = (gint) floor (pixheight * current_level);
581
582         /* reset the height & origin of the rect that needs to show the pixbuf
583          */
584
585         pixrect.height = top_of_meter;
586         pixrect.y = pixheight - top_of_meter;
587
588         background.x = 0;
589         background.y = 0;
590         background.width = pixrect.width;
591         background.height = pixheight - top_of_meter;
592
593         eventarea.x = area->x;
594         eventarea.y = area->y;
595         eventarea.width = area->width;
596         eventarea.height = area->height;
597
598         // Switching to CAIRO we would like to draw on the container's bkg.
599         // if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
600         // cairo_set_source (cr, bgpattern->cobj());
601         // cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
602         // cairo_fill (cr);
603         // }
604
605         // MEMO: Normaly MATURE OS clips so called invalidated rects itself making APP free of
606         //       heavy operations which OS does with graphic HW
607
608         // NOTE FROM PAUL: GTK does clip already. The invalidated rect isn't the only area 
609         // we want to clip to however, which is why this object/class is called FastMeter.
610         // I have left stuff commented out as I found it when I merged from Ardour in August 2014,
611         // but this commenting and the previous MEMO comment represent a misunderstanding
612         // of what this code is doing.
613
614         // if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
615                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
616                 //cairo_set_source (cr, fgpattern->cobj());
617                 cairo_set_source_rgba (cr, 0.69, 0.69, 0.69, 1);
618                 cairo_rectangle (cr, pixrect.x, pixrect.y, pixrect.width, pixrect.height);
619                 cairo_fill (cr);
620         //}
621
622         // draw peak bar
623
624         if (hold_state) {
625                 last_peak_rect.x = 0;
626                 last_peak_rect.width = pixwidth;
627                 last_peak_rect.y = max(0, pixheight - (gint) floor (pixheight * current_peak));
628                 if (bright_hold || (_styleflags & 2)) {
629                         last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y ));
630                 } else {
631                         last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y ));
632                 }
633
634                 cairo_set_source (cr, fgpattern->cobj());
635                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
636
637                 if (bright_hold && !no_rgba_overlay) {
638                         cairo_fill_preserve (cr);
639                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
640                 }
641                 cairo_fill (cr);
642
643         } else {
644                 last_peak_rect.width = 0;
645                 last_peak_rect.height = 0;
646         }
647 }
648
649 void
650 FastMeter::horizontal_expose (cairo_t* cr, cairo_rectangle_t* area)
651 {
652         gint right_of_meter;
653
654         //cairo_set_source_rgb (cr, 0, 0, 0); // black
655         //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
656         //cairo_stroke (cr);
657
658         right_of_meter = (gint) floor (pixwidth * current_level);
659
660         /* reset the height & origin of the rect that needs to show the pixbuf
661          */
662
663         pixrect.width = right_of_meter;
664
665         // draw peak bar
666
667         if (hold_state) {
668                 last_peak_rect.y = 1;
669                 last_peak_rect.height = pixheight;
670                 const int xpos = floor (pixwidth * current_peak);
671                 if (bright_hold || (_styleflags & 2)) {
672                         last_peak_rect.width = min(3, xpos );
673                 } else {
674                         last_peak_rect.width = min(2, xpos );
675                 }
676                 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
677
678                 cairo_set_source (cr, fgpattern->cobj());
679                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
680
681                 if (bright_hold && !no_rgba_overlay) {
682                         cairo_fill_preserve (cr);
683                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
684                 }
685                 cairo_fill (cr);
686
687         } else {
688                 last_peak_rect.width = 0;
689                 last_peak_rect.height = 0;
690         }
691 }
692
693 void
694 FastMeter::set (float lvl, float peak)
695 {
696         float old_level = current_level;
697         float old_peak = current_peak;
698
699         if (pixwidth <= 0 || pixheight <=0) return;
700
701         if (peak == -1) {
702                 if (lvl >= current_peak) {
703                         current_peak = lvl;
704                         hold_state = hold_cnt;
705                 }
706
707                 if (hold_state > 0) {
708                         if (--hold_state == 0) {
709                                 current_peak = lvl;
710                         }
711                 }
712                 bright_hold = false;
713         } else {
714                 current_peak = peak;
715                 hold_state = 1;
716                 bright_hold = true;
717         }
718
719         current_level = lvl;
720
721         if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
722                 return;
723         }
724
725         Glib::RefPtr<Gdk::Window> win;
726
727         if ((win = get_window()) == 0) {
728                 queue_draw ();
729                 return;
730         }
731
732         if (orientation == Vertical) {
733                 queue_vertical_redraw (win, old_level);
734         } else {
735                 queue_horizontal_redraw (win, old_level);
736         }
737 }
738
739 void
740 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
741 {
742         GdkRectangle rect;
743
744         gint new_top = (gint) floor (pixheight * current_level);
745
746         rect.x = 0;
747         rect.width = pixwidth;
748         rect.height = new_top;
749         rect.y = pixheight - new_top;
750
751         if (current_level > old_level) {
752                 /* colored/pixbuf got larger, just draw the new section */
753                 /* rect.y stays where it is because of X coordinates */
754                 /* height of invalidated area is between new.y (smaller) and old.y
755                    (larger).
756                    X coordinates just make my brain hurt.
757                 */
758                 rect.height = pixrect.y - rect.y;
759         } else {
760                 /* it got smaller, compute the difference */
761                 /* rect.y becomes old.y (the smaller value) */
762                 rect.y = pixrect.y;
763                 /* rect.height is the old.y (smaller) minus the new.y (larger)
764                 */
765                 rect.height = pixrect.height - rect.height;
766         }
767
768         GdkRegion* region = 0;
769         bool queue = false;
770
771         if (rect.height != 0) {
772
773                 /* ok, first region to draw ... */
774
775                 region = gdk_region_rectangle (&rect);
776                 queue = true;
777         }
778
779         /* redraw the last place where the last peak hold bar was;
780            the next expose will draw the new one whether its part of
781            expose region or not.
782         */
783
784         if (last_peak_rect.width * last_peak_rect.height != 0) {
785                 if (!queue) {
786                         region = gdk_region_new ();
787                         queue = true;
788                 }
789                 gdk_region_union_with_rect (region, &last_peak_rect);
790         }
791
792         if (hold_state && current_peak > 0) {
793                 if (!queue) {
794                         region = gdk_region_new ();
795                         queue = true;
796                 }
797                 rect.x = 1;
798                 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
799                 if (bright_hold || (_styleflags & 2)) {
800                         rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
801                 } else {
802                         rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
803                 }
804                 rect.width = pixwidth;
805                 gdk_region_union_with_rect (region, &rect);
806         }
807
808         if (queue) {
809                 gdk_window_invalidate_region (win->gobj(), region, true);
810         }
811         if (region) {
812                 gdk_region_destroy(region);
813                 region = 0;
814         }
815 }
816
817 void
818 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
819 {
820         GdkRectangle rect;
821
822         gint new_right = (gint) floor (pixwidth * current_level);
823
824         rect.height = pixheight;
825         rect.y = 1;
826
827         if (current_level > old_level) {
828                 rect.x = 1 + pixrect.width;
829                 /* colored/pixbuf got larger, just draw the new section */
830                 rect.width = new_right - pixrect.width;
831         } else {
832                 /* it got smaller, compute the difference */
833                 rect.x = 1 + new_right;
834                 /* rect.height is the old.x (smaller) minus the new.x (larger) */
835                 rect.width = pixrect.width - new_right;
836         }
837
838         GdkRegion* region = 0;
839         bool queue = false;
840
841         if (rect.height != 0) {
842
843                 /* ok, first region to draw ... */
844
845                 region = gdk_region_rectangle (&rect);
846                 queue = true;
847         }
848
849         /* redraw the last place where the last peak hold bar was;
850            the next expose will draw the new one whether its part of
851            expose region or not.
852         */
853
854         if (last_peak_rect.width * last_peak_rect.height != 0) {
855                 if (!queue) {
856                         region = gdk_region_new ();
857                         queue = true;
858                 }
859                 gdk_region_union_with_rect (region, &last_peak_rect);
860         }
861
862         if (hold_state && current_peak > 0) {
863                 if (!queue) {
864                         region = gdk_region_new ();
865                         queue = true;
866                 }
867                 rect.y = 1;
868                 rect.height = pixheight;
869                 const int xpos = floor (pixwidth * current_peak);
870                 if (bright_hold || (_styleflags & 2)) {
871                         rect.width = min(3, xpos);
872                 } else {
873                         rect.width = min(2, xpos);
874                 }
875                 rect.x = 1 + max(0, xpos - rect.width);
876                 gdk_region_union_with_rect (region, &rect);
877         }
878
879         if (queue) {
880                 gdk_window_invalidate_region (win->gobj(), region, true);
881         }
882         if (region) {
883                 gdk_region_destroy(region);
884                 region = 0;
885         }
886 }
887
888 void
889 FastMeter::set_highlight (bool onoff)
890 {
891         if (highlight == onoff) {
892                 return;
893         }
894         highlight = onoff;
895         if (orientation == Vertical) {
896                 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);
897         } else {
898                 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);
899         }
900         queue_draw ();
901 }
902
903 void
904 FastMeter::clear ()
905 {
906         current_level = 0;
907         current_peak = 0;
908         hold_state = 0;
909         queue_draw ();
910 }