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