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