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