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