remove unused variables
[ardour.git] / libs / gtkmm2ext / waves_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(1)
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 = 0;
105         pixrect.y = 0;
106
107         if (!len) {
108                 len = 250;
109         }
110         if (orientation == Vertical) {
111                 pixheight = len;
112                 pixwidth = dimen;
113                 fgpattern = request_vertical_meter(pixwidth, pixheight, _clr, _stp, _styleflags);
114                 bgpattern = request_vertical_background (pixwidth, pixheight, _bgc, false);
115
116         } else {
117                 pixheight = dimen;
118                 pixwidth = len;
119                 fgpattern = request_horizontal_meter(pixwidth, pixheight, _clr, _stp, _styleflags);
120                 bgpattern = request_horizontal_background (pixwidth, pixheight, _bgc, false);
121         }
122
123         pixrect.width = pixwidth;
124         pixrect.height = pixheight;
125
126         request_width = pixrect.width;
127         request_height= pixrect.height;
128
129         clear ();
130 }
131
132 FastMeter::~FastMeter ()
133 {
134 }
135
136 void
137 FastMeter::flush_pattern_cache ()
138 {
139         Pattern10Map::iterator i1;
140         PatternBgMap::iterator ib;
141
142         for (ib = hb_pattern_cache.begin(); ib !=  hb_pattern_cache.end(); ++ib) {
143                 hb_pattern_cache.erase(ib);
144         }
145
146         for (i1 = hm_pattern_cache.begin(); i1 !=  hm_pattern_cache.end(); ++i1) {
147                 hm_pattern_cache.erase(i1);
148         }
149
150         for (ib = vb_pattern_cache.begin(); ib !=  vb_pattern_cache.end(); ++ib) {
151                 vb_pattern_cache.erase(ib);
152         }
153
154         for (i1 = vm_pattern_cache.begin(); i1 !=  vm_pattern_cache.end(); ++i1) {
155                 vm_pattern_cache.erase(i1);
156         }
157 }
158
159 Cairo::RefPtr<Cairo::Pattern>
160 FastMeter::generate_meter_pattern (
161                 int width, int height, int *clr, float *stp, int styleflags, bool horiz)
162 {
163         guint8 r,g,b,a;
164         double knee;
165         const double soft =  3.0 / (double) height;
166         const double offs = -1.0 / (double) height;
167
168         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
169
170         /*
171           Cairo coordinate space goes downwards as y value goes up, so invert
172           knee-based positions by using (1.0 - y)
173         */
174
175         UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
176         cairo_pattern_add_color_stop_rgb (pat, 0.0,
177                                           r/255.0, g/255.0, b/255.0);
178
179         knee = offs + stp[3] / 115.0f; // -0dB
180
181         UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
182         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
183                                           r/255.0, g/255.0, b/255.0);
184
185         UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
186         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
187                                           r/255.0, g/255.0, b/255.0);
188
189         knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
190
191         UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
192         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
193                                           r/255.0, g/255.0, b/255.0);
194
195         UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
196         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
197                                           r/255.0, g/255.0, b/255.0);
198
199         knee = offs + stp[1] / 115.0f; // -9dB
200
201         UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
202         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
203                                           r/255.0, g/255.0, b/255.0);
204
205         UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
206         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
207                                           r/255.0, g/255.0, b/255.0);
208
209         knee = offs + stp[0] / 115.0f; // -18dB
210
211         UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
212         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
213                                           r/255.0, g/255.0, b/255.0);
214
215         UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
216         cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
217                                           r/255.0, g/255.0, b/255.0);
218
219         UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
220         cairo_pattern_add_color_stop_rgb (pat, 1.0,
221                                           r/255.0, g/255.0, b/255.0);
222
223         if ((styleflags & 1) && !no_rgba_overlay) {
224                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
225                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0,   0.0, 0.0, 0.0, 0.15);
226                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
227                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1,   0.0, 0.0, 0.0, 0.25);
228
229                 cairo_surface_t* surface;
230                 cairo_t* tc = 0;
231                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
232                 tc = cairo_create (surface);
233                 cairo_set_source (tc, pat);
234                 cairo_rectangle (tc, 0, 0, width, height);
235                 cairo_fill (tc);
236                 cairo_pattern_destroy (pat);
237
238                 cairo_set_source (tc, shade_pattern);
239                 cairo_rectangle (tc, 0, 0, width, height);
240                 cairo_fill (tc);
241                 cairo_pattern_destroy (shade_pattern);
242
243                 if (styleflags & 2) { // LED stripes
244                         cairo_save (tc);
245                         cairo_set_line_width(tc, 1.0);
246                         cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
247                         //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
248                         for (float y=0.5; y < height; y+= 2.0) {
249                                 cairo_move_to(tc, 0, y);
250                                 cairo_line_to(tc, width, y);
251                                 cairo_stroke (tc);
252                         }
253                         cairo_restore (tc);
254                 }
255
256                 pat = cairo_pattern_create_for_surface (surface);
257                 cairo_destroy (tc);
258                 cairo_surface_destroy (surface);
259         }
260
261         if (horiz) {
262                 cairo_surface_t* surface;
263                 cairo_t* tc = 0;
264                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
265                 tc = cairo_create (surface);
266
267                 cairo_matrix_t m;
268                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
269                 cairo_matrix_translate (&m, -height, 0);
270                 cairo_pattern_set_matrix (pat, &m);
271                 cairo_set_source (tc, pat);
272                 cairo_rectangle (tc, 0, 0, height, width);
273                 cairo_fill (tc);
274                 cairo_pattern_destroy (pat);
275                 pat = cairo_pattern_create_for_surface (surface);
276                 cairo_destroy (tc);
277                 cairo_surface_destroy (surface);
278         }
279         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
280
281         return p;
282 }
283
284
285 Cairo::RefPtr<Cairo::Pattern>
286 FastMeter::generate_meter_background (
287                 int width, int height, int *clr, bool shade, bool horiz)
288 {
289         guint8 r0,g0,b0,r1,g1,b1,a;
290
291         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
292
293         UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
294         UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
295
296         cairo_pattern_add_color_stop_rgb (pat, 0.0,
297                                           r1/255.0, g1/255.0, b1/255.0);
298
299         cairo_pattern_add_color_stop_rgb (pat, 1.0,
300                                           r0/255.0, g0/255.0, b0/255.0);
301
302         if (shade && !no_rgba_overlay) {
303                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
304                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
305                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
306                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
307
308                 cairo_surface_t* surface;
309                 cairo_t* tc = 0;
310                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
311                 tc = cairo_create (surface);
312                 cairo_set_source (tc, pat);
313                 cairo_rectangle (tc, 0, 0, width, height);
314                 cairo_fill (tc);
315                 cairo_set_source (tc, shade_pattern);
316                 cairo_rectangle (tc, 0, 0, width, height);
317                 cairo_fill (tc);
318
319                 cairo_pattern_destroy (pat);
320                 cairo_pattern_destroy (shade_pattern);
321
322                 pat = cairo_pattern_create_for_surface (surface);
323
324                 cairo_destroy (tc);
325                 cairo_surface_destroy (surface);
326         }
327
328         if (horiz) {
329                 cairo_surface_t* surface;
330                 cairo_t* tc = 0;
331                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
332                 tc = cairo_create (surface);
333
334                 cairo_matrix_t m;
335                 cairo_matrix_init_rotate (&m, -M_PI/2.0);
336                 cairo_matrix_translate (&m, -height, 0);
337                 cairo_pattern_set_matrix (pat, &m);
338                 cairo_set_source (tc, pat);
339                 cairo_rectangle (tc, 0, 0, height, width);
340                 cairo_fill (tc);
341                 cairo_pattern_destroy (pat);
342                 pat = cairo_pattern_create_for_surface (surface);
343                 cairo_destroy (tc);
344                 cairo_surface_destroy (surface);
345         }
346
347         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
348
349         return p;
350 }
351
352 Cairo::RefPtr<Cairo::Pattern>
353 FastMeter::request_vertical_meter(
354                 int width, int height, int *clr, float *stp, int styleflags)
355 {
356         height = max(height, min_pattern_metric_size);
357         height = min(height, max_pattern_metric_size);
358
359         const Pattern10MapKey key (width, height,
360                         stp[0], stp[1], stp[2], stp[3],
361                         clr[0], clr[1], clr[2], clr[3],
362                         clr[4], clr[5], clr[6], clr[7],
363                         clr[8], clr[9], styleflags);
364
365         Pattern10Map::iterator i;
366         if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
367                 return i->second;
368         }
369         // TODO flush pattern cache if it gets too large
370
371         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
372                 width, height, clr, stp, styleflags, false);
373         vm_pattern_cache[key] = p;
374
375         return p;
376 }
377
378 Cairo::RefPtr<Cairo::Pattern>
379 FastMeter::request_vertical_background(
380         int width, int height, int *bgc, bool /*shade */)
381 {
382         height = max(height, min_pattern_metric_size);
383         height = min(height, max_pattern_metric_size);
384         height += 2;
385
386         const PatternBgMapKey key (width, height, bgc[0], bgc[1], false);
387         PatternBgMap::iterator i;
388         if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
389                 return i->second;
390         }
391         // TODO flush pattern cache if it gets too large
392
393         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
394                 width, height, bgc, false, false);
395         vb_pattern_cache[key] = p;
396
397         return p;
398 }
399
400 Cairo::RefPtr<Cairo::Pattern>
401 FastMeter::request_horizontal_meter(
402                 int width, int height, int *clr, float *stp, int styleflags)
403 {
404         width = max(width, min_pattern_metric_size);
405         width = min(width, max_pattern_metric_size);
406
407         const Pattern10MapKey key (width, height,
408                         stp[0], stp[1], stp[2], stp[3],
409                         clr[0], clr[1], clr[2], clr[3],
410                         clr[4], clr[5], clr[6], clr[7],
411                         clr[8], clr[9], styleflags);
412
413         Pattern10Map::iterator i;
414         if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
415                 return i->second;
416         }
417         // TODO flush pattern cache if it gets too large
418
419         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
420                 height, width, clr, stp, styleflags, true);
421
422         hm_pattern_cache[key] = p;
423         return p;
424 }
425
426 Cairo::RefPtr<Cairo::Pattern>
427 FastMeter::request_horizontal_background(
428         int width, int height, int *bgc, bool /* shade */)
429 {
430         width = max(width, min_pattern_metric_size);
431         width = min(width, max_pattern_metric_size);
432         width += 2;
433
434         const PatternBgMapKey key (width, height, bgc[0], bgc[1], false);
435         PatternBgMap::iterator i;
436         if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
437                 return i->second;
438         }
439         // TODO flush pattern cache if it gets too large
440
441         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
442                 height, width, bgc, false, true);
443
444         hb_pattern_cache[key] = p;
445
446         return p;
447 }
448
449
450
451 void
452 FastMeter::set_hold_count (long val)
453 {
454         if (val < 1) {
455                 val = 1;
456         }
457
458         hold_cnt = val;
459         hold_state = 0;
460         current_peak = 0;
461
462         queue_draw ();
463 }
464
465 void
466 FastMeter::on_size_request (GtkRequisition* req)
467 {
468         if (orientation == Vertical) {
469                 vertical_size_request (req);
470         } else {
471                 horizontal_size_request (req);
472         }
473 }
474
475 void
476 FastMeter::vertical_size_request (GtkRequisition* req)
477 {
478         req->height = request_height;
479         req->height = max(req->height, min_pattern_metric_size);
480         req->height = min(req->height, max_pattern_metric_size);
481         req->height += 2;
482
483         req->width  = request_width;
484 }
485
486 void
487 FastMeter::horizontal_size_request (GtkRequisition* req)
488 {
489         req->width = request_width;
490         req->width = max(req->width, min_pattern_metric_size);
491         req->width = min(req->width, max_pattern_metric_size);
492         req->width += 2;
493
494         req->height  = request_height;
495 }
496
497 void
498 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
499 {
500         if (orientation == Vertical) {
501                 vertical_size_allocate (alloc);
502         } else {
503                 horizontal_size_allocate (alloc);
504         }
505         queue_draw ();
506 }
507
508 void
509 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
510 {
511         if (alloc.get_width() != request_width) {
512                 alloc.set_width (request_width);
513         }
514
515         int h = alloc.get_height();
516         h = max (h, min_pattern_metric_size + 2);
517         h = min (h, max_pattern_metric_size + 2);
518
519         if (h != alloc.get_height()) {
520                 alloc.set_height (h);
521         }
522
523         if (pixheight != h) {
524                 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
525                 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, false);
526                 pixheight = h;
527                 pixwidth  = request_width;
528         }
529
530         CairoWidget::on_size_allocate (alloc);
531 }
532
533 void
534 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
535 {
536         if (alloc.get_height() != request_height) {
537                 alloc.set_height (request_height);
538         }
539
540         int w = alloc.get_width();
541         w = max (w, min_pattern_metric_size + 2);
542         w = min (w, max_pattern_metric_size + 2);
543
544         if (w != alloc.get_width()) {
545                 alloc.set_width (w);
546         }
547
548         if (pixwidth != w) {
549                 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
550                 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, false);
551                 pixwidth = w;
552                 pixheight  = request_height;
553         }
554
555         CairoWidget::on_size_allocate (alloc);
556 }
557
558 void
559 FastMeter::render (cairo_t* cr, cairo_rectangle_t* area)
560 {
561         if (orientation == Vertical) {
562                 return vertical_expose (cr, area);
563         } else {
564                 return horizontal_expose (cr, area);
565         }
566 }
567
568 void
569 FastMeter::vertical_expose (cairo_t* cr, cairo_rectangle_t* area)
570 {
571         gint top_of_meter;
572
573         //cairo_set_source_rgb (cr, 0, 0, 0); // black
574         //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
575         //cairo_stroke (cr);
576
577         top_of_meter = (gint) floor (pixheight * current_level);
578
579         /* reset the height & origin of the rect that needs to show the pixbuf
580          */
581
582         pixrect.height = top_of_meter;
583         pixrect.y = pixheight - top_of_meter;
584
585         background.x = 0;
586         background.y = 0;
587         background.width = pixrect.width;
588         background.height = pixheight - top_of_meter;
589
590         eventarea.x = area->x;
591         eventarea.y = area->y;
592         eventarea.width = area->width;
593         eventarea.height = area->height;
594
595         // Switching to CAIRO we would like to draw on the container's bkg.
596         // if (gdk_rectangle_intersect (&background, &eventarea, &intersection)) {
597         // cairo_set_source (cr, bgpattern->cobj());
598         // cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
599         // cairo_fill (cr);
600         // }
601
602         // MEMO: Normaly MATURE OS clips so called invalidated rects itself making APP free of
603         //       heavy operations which OS does with graphic HW
604
605         // NOTE FROM PAUL: GTK does clip already. The invalidated rect isn't the only area 
606         // we want to clip to however, which is why this object/class is called FastMeter.
607         // I have left stuff commented out as I found it when I merged from Ardour in August 2014,
608         // but this commenting and the previous MEMO comment represent a misunderstanding
609         // of what this code is doing.
610
611         // if (gdk_rectangle_intersect (&pixrect, &eventarea, &intersection)) {
612                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
613                 //cairo_set_source (cr, fgpattern->cobj());
614                 cairo_set_source_rgba (cr, 0.69, 0.69, 0.69, 1);
615                 cairo_rectangle (cr, pixrect.x, pixrect.y, pixrect.width, pixrect.height);
616                 cairo_fill (cr);
617         //}
618
619         // draw peak bar
620
621         if (hold_state) {
622                 last_peak_rect.x = 0;
623                 last_peak_rect.width = pixwidth;
624                 last_peak_rect.y = max(0, pixheight - (gint) floor (pixheight * current_peak));
625                 if (bright_hold || (_styleflags & 2)) {
626                         last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y ));
627                 } else {
628                         last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y ));
629                 }
630
631                 cairo_set_source (cr, fgpattern->cobj());
632                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
633
634                 if (bright_hold && !no_rgba_overlay) {
635                         cairo_fill_preserve (cr);
636                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
637                 }
638                 cairo_fill (cr);
639
640         } else {
641                 last_peak_rect.width = 0;
642                 last_peak_rect.height = 0;
643         }
644 }
645
646 void
647 FastMeter::horizontal_expose (cairo_t* cr, cairo_rectangle_t* area)
648 {
649         gint right_of_meter;
650
651         //cairo_set_source_rgb (cr, 0, 0, 0); // black
652         //rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
653         //cairo_stroke (cr);
654
655         right_of_meter = (gint) floor (pixwidth * current_level);
656
657         /* reset the height & origin of the rect that needs to show the pixbuf
658          */
659
660         pixrect.width = right_of_meter;
661
662         // draw peak bar
663
664         if (hold_state) {
665                 last_peak_rect.y = 1;
666                 last_peak_rect.height = pixheight;
667                 const int xpos = floor (pixwidth * current_peak);
668                 if (bright_hold || (_styleflags & 2)) {
669                         last_peak_rect.width = min(3, xpos );
670                 } else {
671                         last_peak_rect.width = min(2, xpos );
672                 }
673                 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
674
675                 cairo_set_source (cr, fgpattern->cobj());
676                 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
677
678                 if (bright_hold && !no_rgba_overlay) {
679                         cairo_fill_preserve (cr);
680                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
681                 }
682                 cairo_fill (cr);
683
684         } else {
685                 last_peak_rect.width = 0;
686                 last_peak_rect.height = 0;
687         }
688 }
689
690 void
691 FastMeter::set (float lvl, float peak)
692 {
693         float old_level = current_level;
694         float old_peak = current_peak;
695
696         if (pixwidth <= 0 || pixheight <=0) return;
697
698         if (peak == -1) {
699                 if (lvl >= current_peak) {
700                         current_peak = lvl;
701                         hold_state = hold_cnt;
702                 }
703
704                 if (hold_state > 0) {
705                         if (--hold_state == 0) {
706                                 current_peak = lvl;
707                         }
708                 }
709                 bright_hold = false;
710         } else {
711                 current_peak = peak;
712                 hold_state = 1;
713                 bright_hold = true;
714         }
715
716         current_level = lvl;
717
718         if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
719                 return;
720         }
721
722         Glib::RefPtr<Gdk::Window> win;
723
724         if ((win = get_window()) == 0) {
725                 queue_draw ();
726                 return;
727         }
728
729         if (orientation == Vertical) {
730                 queue_vertical_redraw (win, old_level);
731         } else {
732                 queue_horizontal_redraw (win, old_level);
733         }
734 }
735
736 void
737 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
738 {
739         GdkRectangle rect;
740
741         gint new_top = (gint) floor (pixheight * current_level);
742
743         rect.x = 0;
744         rect.width = pixwidth;
745         rect.height = new_top;
746         rect.y = pixheight - new_top;
747
748         if (current_level > old_level) {
749                 /* colored/pixbuf got larger, just draw the new section */
750                 /* rect.y stays where it is because of X coordinates */
751                 /* height of invalidated area is between new.y (smaller) and old.y
752                    (larger).
753                    X coordinates just make my brain hurt.
754                 */
755                 rect.height = pixrect.y - rect.y;
756         } else {
757                 /* it got smaller, compute the difference */
758                 /* rect.y becomes old.y (the smaller value) */
759                 rect.y = pixrect.y;
760                 /* rect.height is the old.y (smaller) minus the new.y (larger)
761                 */
762                 rect.height = pixrect.height - rect.height;
763         }
764
765         GdkRegion* region = 0;
766         bool queue = false;
767
768         if (rect.height != 0) {
769
770                 /* ok, first region to draw ... */
771
772                 region = gdk_region_rectangle (&rect);
773                 queue = true;
774         }
775
776         /* redraw the last place where the last peak hold bar was;
777            the next expose will draw the new one whether its part of
778            expose region or not.
779         */
780
781         if (last_peak_rect.width * last_peak_rect.height != 0) {
782                 if (!queue) {
783                         region = gdk_region_new ();
784                         queue = true;
785                 }
786                 gdk_region_union_with_rect (region, &last_peak_rect);
787         }
788
789         if (hold_state && current_peak > 0) {
790                 if (!queue) {
791                         region = gdk_region_new ();
792                         queue = true;
793                 }
794                 rect.x = 1;
795                 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
796                 if (bright_hold || (_styleflags & 2)) {
797                         rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
798                 } else {
799                         rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
800                 }
801                 rect.width = pixwidth;
802                 gdk_region_union_with_rect (region, &rect);
803         }
804
805         if (queue) {
806                 gdk_window_invalidate_region (win->gobj(), region, true);
807         }
808         if (region) {
809                 gdk_region_destroy(region);
810                 region = 0;
811         }
812 }
813
814 void
815 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
816 {
817         GdkRectangle rect;
818
819         gint new_right = (gint) floor (pixwidth * current_level);
820
821         rect.height = pixheight;
822         rect.y = 1;
823
824         if (current_level > old_level) {
825                 rect.x = 1 + pixrect.width;
826                 /* colored/pixbuf got larger, just draw the new section */
827                 rect.width = new_right - pixrect.width;
828         } else {
829                 /* it got smaller, compute the difference */
830                 rect.x = 1 + new_right;
831                 /* rect.height is the old.x (smaller) minus the new.x (larger) */
832                 rect.width = pixrect.width - new_right;
833         }
834
835         GdkRegion* region = 0;
836         bool queue = false;
837
838         if (rect.height != 0) {
839
840                 /* ok, first region to draw ... */
841
842                 region = gdk_region_rectangle (&rect);
843                 queue = true;
844         }
845
846         /* redraw the last place where the last peak hold bar was;
847            the next expose will draw the new one whether its part of
848            expose region or not.
849         */
850
851         if (last_peak_rect.width * last_peak_rect.height != 0) {
852                 if (!queue) {
853                         region = gdk_region_new ();
854                         queue = true;
855                 }
856                 gdk_region_union_with_rect (region, &last_peak_rect);
857         }
858
859         if (hold_state && current_peak > 0) {
860                 if (!queue) {
861                         region = gdk_region_new ();
862                         queue = true;
863                 }
864                 rect.y = 1;
865                 rect.height = pixheight;
866                 const int xpos = floor (pixwidth * current_peak);
867                 if (bright_hold || (_styleflags & 2)) {
868                         rect.width = min(3, xpos);
869                 } else {
870                         rect.width = min(2, xpos);
871                 }
872                 rect.x = 1 + max(0, xpos - rect.width);
873                 gdk_region_union_with_rect (region, &rect);
874         }
875
876         if (queue) {
877                 gdk_window_invalidate_region (win->gobj(), region, true);
878         }
879         if (region) {
880                 gdk_region_destroy(region);
881                 region = 0;
882         }
883 }
884
885 void
886 FastMeter::set_highlight (bool onoff)
887 {
888         if (highlight == onoff) {
889                 return;
890         }
891         highlight = onoff;
892         if (orientation == Vertical) {
893                 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);
894         } else {
895                 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, false);
896         }
897         queue_draw ();
898 }
899
900 void
901 FastMeter::clear ()
902 {
903         current_level = 0;
904         current_peak = 0;
905         hold_state = 0;
906         queue_draw ();
907 }