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