make all meter-colors configurable,..
[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 = 10;
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, int bgc0, int bgc1,
48                 float stp0, float stp1,
49                 float stp2, float stp3
50                 )
51 {
52         orientation = o;
53         hold_cnt = hold;
54         resized = true;
55         hold_state = 0;
56         current_peak = 0;
57         current_level = 0;
58         last_peak_rect.width = 0;
59         last_peak_rect.height = 0;
60
61         _clr[0] = clr0;
62         _clr[1] = clr1;
63         _clr[2] = clr2;
64         _clr[3] = clr3;
65         _clr[4] = clr4;
66         _clr[5] = clr5;
67         _clr[6] = clr6;
68         _clr[7] = clr7;
69         _clr[8] = clr8;
70         _clr[9] = clr9;
71
72         _bgc[0] = bgc0;
73         _bgc[1] = bgc1;
74
75         _stp[0] = stp0;
76         _stp[1] = stp1;
77         _stp[2] = stp2;
78         _stp[3] = stp3;
79
80         set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
81
82         pixrect.x = 1;
83         pixrect.y = 1;
84
85         if (!len) {
86                 len = 250;
87         }
88         fgpattern = request_vertical_meter(dimen, len, _clr, _stp);
89         bgpattern = request_vertical_background (dimen, len, _bgc);
90         pixheight = len;
91         pixwidth = dimen;
92
93         pixrect.width = pixwidth;
94         pixrect.height = pixheight;
95
96         request_width = pixrect.width + 2;
97         request_height= pixrect.height + 2;
98 }
99
100 FastMeter::~FastMeter ()
101 {
102 }
103
104 Cairo::RefPtr<Cairo::Pattern>
105 FastMeter::generate_meter_pattern (
106                 int width, int height, int *clr, float *stp)
107 {
108         guint8 r,g,b,a;
109         int knee;
110
111         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, width, height);
112
113         /*
114           Cairo coordinate space goes downwards as y value goes up, so invert
115           knee-based positions by using (1.0 - y)
116         */
117
118         UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
119         cairo_pattern_add_color_stop_rgb (pat, 0.0,
120                                           r/255.0, g/255.0, b/255.0);
121
122         knee = (int)floor((float)height * stp[3] / 115.0f); // -0dB
123
124         UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
125         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
126                                           r/255.0, g/255.0, b/255.0);
127
128         UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
129         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
130                                           r/255.0, g/255.0, b/255.0);
131
132         knee = (int)floor((float)height * stp[2]/ 115.0f); // -3dB || -2dB
133
134         UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
135         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
136                                           r/255.0, g/255.0, b/255.0);
137
138         UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
139         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
140                                           r/255.0, g/255.0, b/255.0);
141
142         knee = (int)floor((float)height * stp[1] / 115.0f); // -9dB
143
144         UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
145         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
146                                           r/255.0, g/255.0, b/255.0);
147
148         UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
149         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
150                                           r/255.0, g/255.0, b/255.0);
151
152         knee = (int)floor((float)height * stp[0] / 115.0f); // -18dB
153
154         UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
155         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
156                                           r/255.0, g/255.0, b/255.0);
157
158         UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
159         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
160                                           r/255.0, g/255.0, b/255.0);
161
162         UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
163         cairo_pattern_add_color_stop_rgb (pat, 1.0,
164                                           r/255.0, g/255.0, b/255.0);
165
166         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
167
168         return p;
169 }
170
171
172 Cairo::RefPtr<Cairo::Pattern>
173 FastMeter::generate_meter_background (
174                 int width, int height, int *clr)
175 {
176         guint8 r0,g0,b0,r1,g1,b1,a;
177
178         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, width, height);
179
180         UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
181         UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
182
183         cairo_pattern_add_color_stop_rgb (pat, 0.0,
184                                           r1/255.0, g1/255.0, b1/255.0);
185
186         cairo_pattern_add_color_stop_rgb (pat, 1.0,
187                                           r0/255.0, g0/255.0, b0/255.0);
188
189         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
190
191         return p;
192 }
193
194 Cairo::RefPtr<Cairo::Pattern>
195 FastMeter::request_vertical_meter(
196                 int width, int height, int *clr, float *stp)
197 {
198         if (height < min_pattern_metric_size)
199                 height = min_pattern_metric_size;
200         if (height > max_pattern_metric_size)
201                 height = max_pattern_metric_size;
202
203         const Pattern10MapKey key (width, height,
204                         stp[0], stp[1], stp[2], stp[3],
205                         clr[0], clr[1], clr[2], clr[3],
206                         clr[4], clr[5], clr[6], clr[7],
207                         clr[8], clr[9]);
208         Pattern10Map::iterator i;
209         if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
210                 return i->second;
211         }
212
213         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
214                 width, height, clr, stp);
215         vm_pattern_cache[key] = p;
216
217         return p;
218 }
219
220 Cairo::RefPtr<Cairo::Pattern>
221 FastMeter::request_vertical_background(
222                 int width, int height, int *bgc)
223 {
224         if (height < min_pattern_metric_size)
225                 height = min_pattern_metric_size;
226         if (height > max_pattern_metric_size)
227                 height = max_pattern_metric_size;
228
229         const PatternBgMapKey key (width, height, bgc[0], bgc[1]);
230         PatternBgMap::iterator i;
231         if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
232                 return i->second;
233         }
234
235         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
236                 width, height, bgc);
237         vb_pattern_cache[key] = p;
238
239         return p;
240 }
241
242
243 void
244 FastMeter::set_hold_count (long val)
245 {
246         if (val < 1) {
247                 val = 1;
248         }
249
250         hold_cnt = val;
251         hold_state = 0;
252         current_peak = 0;
253
254         queue_draw ();
255 }
256
257 void
258 FastMeter::on_size_request (GtkRequisition* req)
259 {
260         req->height = request_height;
261         req->height = max(req->height, min_pattern_metric_size);
262         req->height = min(req->height, max_pattern_metric_size);
263         req->height += 2;
264
265         req->width  = request_width;
266 }
267
268 void
269 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
270 {
271         if (alloc.get_width() != request_width) {
272                 alloc.set_width (request_width);
273         }
274
275         int h = alloc.get_height();
276         h = max (h, min_pattern_metric_size + 2);
277         h = min (h, max_pattern_metric_size + 2);
278
279         if (h != alloc.get_height()) {
280                 alloc.set_height (h);
281         }
282
283         if (pixheight != h) {
284                 fgpattern = request_vertical_meter (request_width, h, _clr, _stp);
285                 bgpattern = request_vertical_background (request_width, h, _bgc);
286                 pixheight = h - 2;
287                 pixwidth  = request_width - 2;
288         }
289
290         DrawingArea::on_size_allocate (alloc);
291         resized = true;
292 }
293
294 bool
295 FastMeter::on_expose_event (GdkEventExpose* ev)
296 {
297         return vertical_expose (ev);
298 }
299
300 bool
301 FastMeter::vertical_expose (GdkEventExpose* ev)
302 {
303         Glib::RefPtr<Gdk::Window> win = get_window ();
304         gint top_of_meter;
305         GdkRectangle intersection;
306         GdkRectangle background;
307
308         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
309
310         if (resized) {
311                 cairo_set_source_rgb (cr, 0, 0, 0); // black
312                 rounded_rectangle (cr, 0, 0, pixrect.width + 2, pixheight + 2, 2);
313                 cairo_fill (cr);
314         }
315
316         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
317         cairo_clip (cr);
318
319         top_of_meter = (gint) floor (pixheight * current_level);
320
321         /* reset the height & origin of the rect that needs to show the pixbuf
322          */
323
324         pixrect.height = top_of_meter;
325         pixrect.y = 1 + pixheight - top_of_meter;
326
327         background.x = 1;
328         background.y = 1;
329         background.width = pixrect.width;
330         background.height = pixheight - top_of_meter;
331
332         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
333                 cairo_set_source (cr, bgpattern->cobj());
334                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
335                 cairo_fill (cr);
336         }
337
338         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
339                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
340                 cairo_set_source (cr, fgpattern->cobj());
341                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
342                 cairo_fill (cr);
343         }
344
345         // draw peak bar
346
347         if (hold_state) {
348                 last_peak_rect.x = 1;
349                 last_peak_rect.width = pixwidth;
350                 last_peak_rect.y = 1 + pixheight - (gint) floor (pixheight * current_peak);
351                 last_peak_rect.height = min(2, pixheight - last_peak_rect.y);
352
353                 cairo_set_source (cr, fgpattern->cobj());
354                 cairo_rectangle (cr, 1, last_peak_rect.y, pixwidth, last_peak_rect.height);
355                 cairo_fill (cr);
356
357         } else {
358                 last_peak_rect.width = 0;
359                 last_peak_rect.height = 0;
360         }
361
362         cairo_destroy (cr);
363
364         return TRUE;
365 }
366
367 void
368 FastMeter::set (float lvl)
369 {
370         float old_level = current_level;
371         float old_peak = current_peak;
372
373         current_level = lvl;
374
375         if (lvl > current_peak) {
376                 current_peak = lvl;
377                 hold_state = hold_cnt;
378         }
379
380         if (hold_state > 0) {
381                 if (--hold_state == 0) {
382                         current_peak = lvl;
383                 }
384         }
385
386         if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
387                 return;
388         }
389
390
391         Glib::RefPtr<Gdk::Window> win;
392
393         if ((win = get_window()) == 0) {
394                 queue_draw ();
395                 return;
396         }
397
398         queue_vertical_redraw (win, old_level);
399 }
400
401 void
402 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
403 {
404         GdkRectangle rect;
405
406         gint new_top = (gint) floor (pixheight * current_level);
407
408         rect.x = 1;
409         rect.width = pixwidth;
410         rect.height = new_top;
411         rect.y = 1 + pixheight - new_top;
412
413         if (current_level > old_level) {
414                 /* colored/pixbuf got larger, just draw the new section */
415                 /* rect.y stays where it is because of X coordinates */
416                 /* height of invalidated area is between new.y (smaller) and old.y
417                    (larger).
418                    X coordinates just make my brain hurt.
419                 */
420                 rect.height = pixrect.y - rect.y;
421         } else {
422                 /* it got smaller, compute the difference */
423                 /* rect.y becomes old.y (the smaller value) */
424                 rect.y = pixrect.y;
425                 /* rect.height is the old.y (smaller) minus the new.y (larger)
426                 */
427                 rect.height = pixrect.height - rect.height;
428         }
429
430         GdkRegion* region = 0;
431         bool queue = false;
432
433         if (rect.height != 0) {
434
435                 /* ok, first region to draw ... */
436
437                 region = gdk_region_rectangle (&rect);
438                 queue = true;
439         }
440
441         /* redraw the last place where the last peak hold bar was;
442            the next expose will draw the new one whether its part of
443            expose region or not.
444         */
445
446         if (last_peak_rect.width * last_peak_rect.height != 0) {
447                 if (!queue) {
448                         region = gdk_region_new ();
449                         queue = true;
450                 }
451                 gdk_region_union_with_rect (region, &last_peak_rect);
452         }
453
454         if (queue) {
455                 gdk_window_invalidate_region (win->gobj(), region, true);
456         }
457         if (region) {
458                 gdk_region_destroy(region);
459                 region = 0;
460         }
461 }
462
463 void
464 FastMeter::clear ()
465 {
466         current_level = 0;
467         current_peak = 0;
468         hold_state = 0;
469         queue_draw ();
470 }