fix initial display of name/id on meterbridge
[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 <gdkmm/rectangle.h>
27 #include <gtkmm2ext/fastmeter.h>
28 #include <gtkmm2ext/utils.h>
29
30 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
31 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
32 using namespace Gtk;
33 using namespace Gdk;
34 using namespace Glib;
35 using namespace Gtkmm2ext;
36 using namespace std;
37
38 int FastMeter::min_pattern_metric_size = 16;
39 int FastMeter::max_pattern_metric_size = 1024;
40
41 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
42 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
43
44 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
45                 int clr0, int clr1, int clr2, int clr3,
46                 int clr4, int clr5, int clr6, int clr7,
47                 int clr8, int clr9,
48                 int bgc0, int bgc1,
49                 int bgh0, int bgh1,
50                 float stp0, float stp1,
51                 float stp2, float stp3
52                 )
53 {
54         orientation = o;
55         hold_cnt = hold;
56         resized = true;
57         hold_state = 0;
58         bright_hold = false;
59         current_peak = 0;
60         current_level = 0;
61         last_peak_rect.width = 0;
62         last_peak_rect.height = 0;
63
64         highlight = false;
65
66         _clr[0] = clr0;
67         _clr[1] = clr1;
68         _clr[2] = clr2;
69         _clr[3] = clr3;
70         _clr[4] = clr4;
71         _clr[5] = clr5;
72         _clr[6] = clr6;
73         _clr[7] = clr7;
74         _clr[8] = clr8;
75         _clr[9] = clr9;
76
77         _bgc[0] = bgc0;
78         _bgc[1] = bgc1;
79
80         _bgh[0] = bgh0;
81         _bgh[1] = bgh1;
82
83         _stp[0] = stp0;
84         _stp[1] = stp1;
85         _stp[2] = stp2;
86         _stp[3] = stp3;
87
88         set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
89
90         pixrect.x = 1;
91         pixrect.y = 1;
92
93         if (!len) {
94                 len = 250;
95         }
96         fgpattern = request_vertical_meter(dimen, len, _clr, _stp, true);
97         bgpattern = request_vertical_background (dimen, len, _bgc, false);
98         pixheight = len;
99         pixwidth = dimen;
100
101         pixrect.width = pixwidth;
102         pixrect.height = pixheight;
103
104         request_width = pixrect.width + 2;
105         request_height= pixrect.height + 2;
106
107         queue_draw ();
108 }
109
110 FastMeter::~FastMeter ()
111 {
112 }
113
114 Cairo::RefPtr<Cairo::Pattern>
115 FastMeter::generate_meter_pattern (
116                 int width, int height, int *clr, float *stp, bool shade)
117 {
118         guint8 r,g,b,a;
119         double knee;
120         double soft = 1.5 / (double) height;
121
122         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
123
124         /*
125           Cairo coordinate space goes downwards as y value goes up, so invert
126           knee-based positions by using (1.0 - y)
127         */
128
129         UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
130         cairo_pattern_add_color_stop_rgb (pat, 0.0,
131                                           r/255.0, g/255.0, b/255.0);
132
133         knee = ((float)height * stp[3] / 115.0f); // -0dB
134
135         UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
136         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
137                                           r/255.0, g/255.0, b/255.0);
138
139         UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
140         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
141                                           r/255.0, g/255.0, b/255.0);
142
143         knee = ((float)height * stp[2]/ 115.0f); // -3dB || -2dB
144
145         UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
146         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
147                                           r/255.0, g/255.0, b/255.0);
148
149         UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
150         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
151                                           r/255.0, g/255.0, b/255.0);
152
153         knee = ((float)height * stp[1] / 115.0f); // -9dB
154
155         UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
156         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
157                                           r/255.0, g/255.0, b/255.0);
158
159         UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
160         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
161                                           r/255.0, g/255.0, b/255.0);
162
163         knee = ((float)height * stp[0] / 115.0f); // -18dB
164
165         UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
166         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) - soft,
167                                           r/255.0, g/255.0, b/255.0);
168
169         UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
170         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height) + soft,
171                                           r/255.0, g/255.0, b/255.0);
172
173         UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
174         cairo_pattern_add_color_stop_rgb (pat, 1.0,
175                                           r/255.0, g/255.0, b/255.0);
176
177         if (shade) {
178                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
179                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1.0, 1.0, 1.0, 0.2);
180                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.3);
181
182                 cairo_surface_t* surface;
183                 cairo_t* tc = 0;
184                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
185                 tc = cairo_create (surface);
186                 cairo_set_source (tc, pat);
187                 cairo_rectangle (tc, 0, 0, width, height);
188                 cairo_fill (tc);
189                 cairo_set_source (tc, shade_pattern);
190                 cairo_rectangle (tc, 0, 0, width, height);
191                 cairo_fill (tc);
192
193                 cairo_pattern_destroy (pat);
194                 cairo_pattern_destroy (shade_pattern);
195
196                 pat = cairo_pattern_create_for_surface (surface);
197
198                 cairo_destroy (tc);
199                 cairo_surface_destroy (surface);
200         }
201
202         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
203
204         return p;
205 }
206
207
208 Cairo::RefPtr<Cairo::Pattern>
209 FastMeter::generate_meter_background (
210                 int width, int height, int *clr, bool shade)
211 {
212         guint8 r0,g0,b0,r1,g1,b1,a;
213
214         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
215
216         UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
217         UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
218
219         cairo_pattern_add_color_stop_rgb (pat, 0.0,
220                                           r1/255.0, g1/255.0, b1/255.0);
221
222         cairo_pattern_add_color_stop_rgb (pat, 1.0,
223                                           r0/255.0, g0/255.0, b0/255.0);
224
225         if (shade) {
226                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
227                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
228                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
229                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
230
231                 cairo_surface_t* surface;
232                 cairo_t* tc = 0;
233                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
234                 tc = cairo_create (surface);
235                 cairo_set_source (tc, pat);
236                 cairo_rectangle (tc, 0, 0, width, height);
237                 cairo_fill (tc);
238                 cairo_set_source (tc, shade_pattern);
239                 cairo_rectangle (tc, 0, 0, width, height);
240                 cairo_fill (tc);
241
242                 cairo_pattern_destroy (pat);
243                 cairo_pattern_destroy (shade_pattern);
244
245                 pat = cairo_pattern_create_for_surface (surface);
246
247                 cairo_destroy (tc);
248                 cairo_surface_destroy (surface);
249         }
250
251         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
252
253         return p;
254 }
255
256 Cairo::RefPtr<Cairo::Pattern>
257 FastMeter::request_vertical_meter(
258                 int width, int height, int *clr, float *stp, bool shade)
259 {
260         if (height < min_pattern_metric_size)
261                 height = min_pattern_metric_size;
262         if (height > max_pattern_metric_size)
263                 height = max_pattern_metric_size;
264
265         const Pattern10MapKey key (width, height,
266                         stp[0], stp[1], stp[2], stp[3],
267                         clr[0], clr[1], clr[2], clr[3],
268                         clr[4], clr[5], clr[6], clr[7],
269                         clr[8], clr[9]);
270
271         Pattern10Map::iterator i;
272         if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
273                 return i->second;
274         }
275         // TODO flush pattern cache if it gets too large
276
277         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
278                 width, height, clr, stp, shade);
279         vm_pattern_cache[key] = p;
280
281         return p;
282 }
283
284 Cairo::RefPtr<Cairo::Pattern>
285 FastMeter::request_vertical_background(
286                 int width, int height, int *bgc, bool shade)
287 {
288         if (height < min_pattern_metric_size)
289                 height = min_pattern_metric_size;
290         if (height > max_pattern_metric_size)
291                 height = max_pattern_metric_size;
292
293         const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
294         PatternBgMap::iterator i;
295         if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
296                 return i->second;
297         }
298         // TODO flush pattern cache if it gets too large
299
300         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
301                 width, height, bgc, shade);
302         vb_pattern_cache[key] = p;
303
304         return p;
305 }
306
307
308 void
309 FastMeter::set_hold_count (long val)
310 {
311         if (val < 1) {
312                 val = 1;
313         }
314
315         hold_cnt = val;
316         hold_state = 0;
317         current_peak = 0;
318
319         queue_draw ();
320 }
321
322 void
323 FastMeter::on_size_request (GtkRequisition* req)
324 {
325         req->height = request_height;
326         req->height = max(req->height, min_pattern_metric_size);
327         req->height = min(req->height, max_pattern_metric_size);
328         req->height += 2;
329
330         req->width  = request_width;
331 }
332
333 void
334 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
335 {
336         if (alloc.get_width() != request_width) {
337                 alloc.set_width (request_width);
338         }
339
340         int h = alloc.get_height();
341         h = max (h, min_pattern_metric_size + 2);
342         h = min (h, max_pattern_metric_size + 2);
343
344         if (h != alloc.get_height()) {
345                 alloc.set_height (h);
346         }
347
348         if (pixheight != h) {
349                 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, true);
350                 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
351                 pixheight = h - 2;
352                 pixwidth  = request_width - 2;
353         }
354
355         DrawingArea::on_size_allocate (alloc);
356         resized = true;
357 }
358
359 bool
360 FastMeter::on_expose_event (GdkEventExpose* ev)
361 {
362         return vertical_expose (ev);
363 }
364
365 bool
366 FastMeter::vertical_expose (GdkEventExpose* ev)
367 {
368         Glib::RefPtr<Gdk::Window> win = get_window ();
369         gint top_of_meter;
370         GdkRectangle intersection;
371         GdkRectangle background;
372
373         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
374
375         if (resized) {
376                 cairo_set_source_rgb (cr, 0, 0, 0); // black
377                 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
378                 cairo_stroke (cr);
379                 //cairo_fill (cr);
380                 //resized = false;
381         }
382         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
383         cairo_clip (cr);
384
385         top_of_meter = (gint) floor (pixheight * current_level);
386
387         /* reset the height & origin of the rect that needs to show the pixbuf
388          */
389
390         pixrect.height = top_of_meter;
391         pixrect.y = 1 + pixheight - top_of_meter;
392
393         background.x = 1;
394         background.y = 1;
395         background.width = pixrect.width;
396         background.height = pixheight - top_of_meter;
397
398         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
399                 cairo_set_source (cr, bgpattern->cobj());
400                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
401                 cairo_fill (cr);
402         }
403
404         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
405                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
406                 cairo_set_source (cr, fgpattern->cobj());
407                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
408                 cairo_fill (cr);
409         }
410
411         // draw peak bar
412
413         if (hold_state) {
414                 last_peak_rect.x = 1;
415                 last_peak_rect.width = pixwidth;
416                 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
417                 if (bright_hold) {
418                         last_peak_rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
419                 } else {
420                         last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
421                 }
422
423                 cairo_set_source (cr, fgpattern->cobj());
424                 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
425                 if (bright_hold) {
426                         cairo_fill_preserve (cr);
427                         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
428                 }
429                 cairo_fill (cr);
430
431         } else {
432                 last_peak_rect.width = 0;
433                 last_peak_rect.height = 0;
434         }
435
436         cairo_destroy (cr);
437
438         return TRUE;
439 }
440
441 void
442 FastMeter::set (float lvl, float peak)
443 {
444         float old_level = current_level;
445         float old_peak = current_peak;
446
447         if (peak == -1) {
448                 if (lvl >= current_peak) {
449                         current_peak = lvl;
450                         hold_state = hold_cnt;
451                 }
452
453                 if (hold_state > 0) {
454                         if (--hold_state == 0) {
455                                 current_peak = lvl;
456                         }
457                 }
458                 bright_hold = false;
459         } else {
460                 current_peak = peak;
461                 hold_state = 1;
462                 bright_hold = true;
463         }
464
465         current_level = lvl;
466
467         if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
468                 return;
469         }
470
471
472         Glib::RefPtr<Gdk::Window> win;
473
474         if ((win = get_window()) == 0) {
475                 queue_draw ();
476                 return;
477         }
478
479         queue_vertical_redraw (win, old_level);
480 }
481
482 void
483 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
484 {
485         GdkRectangle rect;
486
487         gint new_top = (gint) floor (pixheight * current_level);
488
489         rect.x = 1;
490         rect.width = pixwidth;
491         rect.height = new_top;
492         rect.y = 1 + pixheight - new_top;
493
494         if (current_level > old_level) {
495                 /* colored/pixbuf got larger, just draw the new section */
496                 /* rect.y stays where it is because of X coordinates */
497                 /* height of invalidated area is between new.y (smaller) and old.y
498                    (larger).
499                    X coordinates just make my brain hurt.
500                 */
501                 rect.height = pixrect.y - rect.y;
502         } else {
503                 /* it got smaller, compute the difference */
504                 /* rect.y becomes old.y (the smaller value) */
505                 rect.y = pixrect.y;
506                 /* rect.height is the old.y (smaller) minus the new.y (larger)
507                 */
508                 rect.height = pixrect.height - rect.height;
509         }
510
511         GdkRegion* region = 0;
512         bool queue = false;
513
514         if (rect.height != 0) {
515
516                 /* ok, first region to draw ... */
517
518                 region = gdk_region_rectangle (&rect);
519                 queue = true;
520         }
521
522         /* redraw the last place where the last peak hold bar was;
523            the next expose will draw the new one whether its part of
524            expose region or not.
525         */
526
527         if (last_peak_rect.width * last_peak_rect.height != 0) {
528                 if (!queue) {
529                         region = gdk_region_new ();
530                         queue = true;
531                 }
532                 gdk_region_union_with_rect (region, &last_peak_rect);
533         }
534
535         if (hold_state && current_peak > 0) {
536                 if (!queue) {
537                         region = gdk_region_new ();
538                         queue = true;
539                 }
540                 rect.x = 1;
541                 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
542                 if (bright_hold) {
543                         rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
544                 } else {
545                         rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
546                 }
547                 rect.width = pixwidth;
548                 gdk_region_union_with_rect (region, &rect);
549         }
550
551         if (queue) {
552                 gdk_window_invalidate_region (win->gobj(), region, true);
553         }
554         if (region) {
555                 gdk_region_destroy(region);
556                 region = 0;
557         }
558 }
559
560 void
561 FastMeter::set_highlight (bool onoff)
562 {
563         if (highlight == onoff) {
564                 return;
565         }
566         highlight = onoff;
567         bgpattern = request_vertical_background (request_width, pixheight, highlight ? _bgh : _bgc, highlight);
568         resized = true;
569         queue_draw ();
570 }
571
572 void
573 FastMeter::clear ()
574 {
575         current_level = 0;
576         current_peak = 0;
577         hold_state = 0;
578         queue_draw ();
579 }