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