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