many important changes to configuration system and specific parameters
[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 <gdkmm/rectangle.h>
25 #include <gtkmm2ext/fastmeter.h>
26 #include <gtkmm2ext/utils.h>
27 #include <gtkmm/style.h>
28 #include <string.h>
29
30 using namespace Gtk;
31 using namespace Gdk;
32 using namespace Glib;
33 using namespace Gtkmm2ext;
34 using namespace std;
35
36
37 int FastMeter::min_v_pixbuf_size = 50;
38 int FastMeter::max_v_pixbuf_size = 1024;
39 Glib::RefPtr<Gdk::Pixbuf>* FastMeter::v_pixbuf_cache = 0;
40
41 int FastMeter::min_h_pixbuf_size = 50;
42 int FastMeter::max_h_pixbuf_size = 1024;
43 Glib::RefPtr<Gdk::Pixbuf>* FastMeter::h_pixbuf_cache = 0;
44
45
46 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o)
47 {
48         orientation = o;
49         hold_cnt = hold;
50         hold_state = 0;
51         current_peak = 0;
52         current_level = 0;
53         current_user_level = -100.0f;
54         
55         set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
56
57         pixrect.x = 0;
58         pixrect.y = 0;
59
60
61         if (orientation == Vertical) {
62                 pixbuf = request_vertical_meter(250);
63         } else {
64                 pixbuf = request_horizontal_meter(186);
65         }
66
67         pixheight = pixbuf->get_height();
68         pixwidth  = pixbuf->get_width();
69
70         if (orientation == Vertical) {
71                 pixrect.width = min (pixwidth, (gint) dimen);
72                 pixrect.height = pixheight;
73         } else {
74                 pixrect.width = pixwidth;
75                 pixrect.height = min (pixheight, (gint) dimen);
76         }
77
78         request_width = pixrect.width;
79         request_height= pixrect.height;
80 }
81
82 Glib::RefPtr<Gdk::Pixbuf> FastMeter::request_vertical_meter(int length)
83 {
84         if (length < min_v_pixbuf_size)
85                 length = min_v_pixbuf_size;
86         if (length > max_v_pixbuf_size)
87                 length = max_v_pixbuf_size;
88         
89         int index = length - 1;
90
91         if (v_pixbuf_cache == 0) {
92                 v_pixbuf_cache = (Glib::RefPtr<Gdk::Pixbuf>*) malloc(sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_v_pixbuf_size);
93                 memset(v_pixbuf_cache,0,sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_v_pixbuf_size);
94         }
95         Glib::RefPtr<Gdk::Pixbuf> ret = v_pixbuf_cache[index];
96         if (ret)
97                 return ret;
98
99         guint8* data;
100         int width = 5;
101         int height = length;
102
103         data = (guint8*) malloc(width*height * 3);
104         
105         guint8 r,g,b;
106         r=0;
107         g=255;
108         b=0;
109
110         // fake log calculation copied from log_meter.h
111         // actual calculation:
112         // log_meter(0.0f) =
113         //  def = (0.0f + 20.0f) * 2.5f + 50f
114         //  return def / 115.0f
115         int knee = (int)floor((float)height * 100.0f / 115.0f);
116         
117         int y;
118         
119         for (y = 0; y < knee / 2; y++) {
120
121                 r = (guint8)floor(255.0 * (float)y/(float)(knee / 2));
122                 
123                 for (int x = 0; x < width; x++) {
124                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
125                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
126                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
127                 }
128         }
129         
130         for (; y < knee; y++) {
131
132                 g = 255 - (guint8)floor(170.0 * (float)(y - knee/ 2)/(float)(knee / 2));
133                 
134                 for (int x = 0; x < width; x++) {
135                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
136                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
137                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
138                 }
139         }
140
141         r=255;
142         g=0;
143         b=0;
144         for (; y < height; y++) {
145                 for (int x = 0; x < width; x++) {
146                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
147                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
148                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
149                 }
150         }
151         
152         ret = Pixbuf::create_from_data(data, COLORSPACE_RGB, false, 8, width, height, width * 3);
153         v_pixbuf_cache[index] = ret;
154
155         return ret;
156 }
157
158 Glib::RefPtr<Gdk::Pixbuf> FastMeter::request_horizontal_meter(int length)
159 {
160         if (length < min_h_pixbuf_size)
161                 length = min_h_pixbuf_size;
162         if (length > max_h_pixbuf_size)
163                 length = max_h_pixbuf_size;
164         
165         int index = length - 1;
166
167         if (h_pixbuf_cache == 0) {
168                 h_pixbuf_cache = (Glib::RefPtr<Gdk::Pixbuf>*) malloc(sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_h_pixbuf_size);
169                 memset(h_pixbuf_cache,0,sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_h_pixbuf_size);
170         }
171         Glib::RefPtr<Gdk::Pixbuf> ret = h_pixbuf_cache[index];
172         if (ret)
173                 return ret;
174
175         guint8* data;
176         int width = length;
177         int height = 5;
178
179         data = (guint8*) malloc(width*height * 3);
180         
181         guint8 r,g,b;
182         r=0;
183         g=255;
184         b=0;
185
186         // fake log calculation copied from log_meter.h
187         // actual calculation:
188         // log_meter(0.0f) =
189         //  def = (0.0f + 20.0f) * 2.5f + 50f
190         //  return def / 115.0f
191         int knee = (int)floor((float)width * 100.0f / 115.0f);
192         
193         int x;
194         
195         for (x = 0; x < knee / 2; x++) {
196
197                 r = (guint8)floor(255.0 * (float)x/(float)(knee / 2));
198                 
199                 for (int y = 0; y < height; y++) {
200                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
201                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
202                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
203                 }
204         }
205         
206         for (; x < knee; x++) {
207
208                 g = 255 - (guint8)floor(170.0 * (float)(x - knee/ 2)/(float)(knee / 2));
209                 
210                 for (int y = 0; y < height; y++) {
211                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
212                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
213                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
214                 }
215         }
216
217         r=255;
218         g=0;
219         b=0;
220         for (; x < width; x++) {
221                 for (int y = 0; y < height; y++) {
222                         data[ (x+(height-y-1)*width) * 3 + 0 ] = r;
223                         data[ (x+(height-y-1)*width) * 3 + 1 ] = g;
224                         data[ (x+(height-y-1)*width) * 3 + 2 ] = b;
225                 }
226         }
227         
228         ret = Pixbuf::create_from_data(data, COLORSPACE_RGB, false, 8, width, height, width * 3);
229         h_pixbuf_cache[index] = ret;
230
231         return ret;
232 }
233
234 FastMeter::~FastMeter ()
235 {
236 }
237
238 void
239 FastMeter::set_hold_count (long val)
240 {
241         if (val < 1) {
242                 val = 1;
243         }
244         
245         hold_cnt = val;
246         hold_state = 0;
247         current_peak = 0;
248         
249         queue_draw ();
250 }
251
252 void
253 FastMeter::on_size_request (GtkRequisition* req)
254 {
255         if (orientation == Vertical) {
256                 req->height = request_height;
257                 
258                 req->height = max(req->height, min_v_pixbuf_size);
259                 req->height = min(req->height, max_v_pixbuf_size);
260
261                 req->width  = 5;
262         } else {
263                 req->width  = request_width;
264
265                 req->width  = max(req->width,  min_h_pixbuf_size);
266                 req->width  = min(req->width,  max_h_pixbuf_size);
267
268                 req->height = 5;
269         }
270
271 }
272
273 void
274 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
275 {
276         if (orientation == Vertical) {
277                 if (alloc.get_width() != 5) {
278                         alloc.set_width(5);
279                 }
280
281                 int h = alloc.get_height();
282                 h = max(h, min_v_pixbuf_size);
283                 h = min(h, max_v_pixbuf_size);
284
285                 if ( h != alloc.get_height())
286                         alloc.set_height(h);
287
288                 if (pixheight != h) {
289                         pixbuf = request_vertical_meter(h);
290                 }
291         } else {
292                 if (alloc.get_height() != 5) {
293                         alloc.set_height(5);
294                 }
295
296                 int w = alloc.get_width();
297                 w = max(w, min_h_pixbuf_size);
298                 w = min(w, max_h_pixbuf_size);
299
300                 if ( w != alloc.get_width())
301                         alloc.set_width(w);
302
303                 if (pixwidth != w) {
304                         pixbuf = request_horizontal_meter(w);
305                 }
306         }
307
308         pixheight = pixbuf->get_height();
309         pixwidth  = pixbuf->get_width();
310
311         DrawingArea::on_size_allocate(alloc);
312 }
313
314 bool
315 FastMeter::on_expose_event (GdkEventExpose* ev)
316 {
317         if (orientation == Vertical) {
318                 return vertical_expose (ev);
319         } else {
320                 return horizontal_expose (ev);
321         }
322 }
323
324 bool
325 FastMeter::vertical_expose (GdkEventExpose* ev)
326 {
327         gint top_of_meter;
328         GdkRectangle intersection;
329         GdkRectangle background;
330
331         top_of_meter = (gint) floor (pixheight * current_level);
332         pixrect.height = top_of_meter;
333
334         background.x = 0;
335         background.y = 0;
336         background.width = pixrect.width;
337         background.height = pixheight - top_of_meter;
338
339     if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
340                 get_window()->draw_rectangle (get_style()->get_black_gc(), true, 
341                                               intersection.x, intersection.y,
342                                               intersection.width, intersection.height);
343         }
344         
345         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
346                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
347                 get_window()->draw_pixbuf(get_style()->get_fg_gc(get_state()), pixbuf, 
348                                           intersection.x, pixheight - top_of_meter,
349                                           intersection.x, pixheight - top_of_meter,
350                                           intersection.width, intersection.height,
351                                           Gdk::RGB_DITHER_NONE, 0, 0);
352         }
353
354         // draw peak bar 
355         if (hold_state && intersection.width > 0) {
356                 gint y = pixheight - (gint) floor (pixheight * current_peak);
357                 int h = min(3, pixheight - y);
358
359                 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
360                                            intersection.x, y,
361                                            intersection.x, y,
362                                            intersection.width, h,
363                                            Gdk::RGB_DITHER_NONE, 0, 0);
364         }
365
366         return TRUE;
367 }
368
369 bool
370 FastMeter::horizontal_expose (GdkEventExpose* ev)
371 {
372         gint right_of_meter;
373         GdkRectangle intersection;
374         GdkRectangle background;
375
376         right_of_meter = (gint) floor (pixwidth * current_level);
377         pixrect.width = right_of_meter;
378
379         background.x = 0;
380         background.y = 0;
381         background.width  = pixwidth - right_of_meter;
382         background.height = pixrect.height;
383
384     if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
385                 get_window()->draw_rectangle (get_style()->get_black_gc(), true, 
386                                               intersection.x + right_of_meter, intersection.y,
387                                               intersection.width, intersection.height);
388         }
389         
390         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
391                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
392                 get_window()->draw_pixbuf(get_style()->get_fg_gc(get_state()), pixbuf, 
393                                           intersection.x, intersection.y,
394                                           intersection.x, intersection.y,
395                                           intersection.width, intersection.height,
396                                           Gdk::RGB_DITHER_NONE, 0, 0);
397         }
398
399         // draw peak bar 
400         // XXX: peaks don't work properly
401         /*
402         if (hold_state && intersection.height > 0) {
403                 gint x = (gint) floor(pixwidth * current_peak);
404
405                 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
406                                            x, intersection.y,
407                                            x, intersection.y,
408                                            3, intersection.height,
409                                            Gdk::RGB_DITHER_NONE, 0, 0);
410         }
411         */
412         
413         return true;
414 }
415
416 void
417 FastMeter::set (float lvl, float usrlvl)
418 {
419         current_level = lvl;
420         current_user_level = usrlvl;
421         
422         if (lvl > current_peak) {
423                 current_peak = lvl;
424                 hold_state = hold_cnt;
425         }
426         
427         if (hold_state > 0) {
428                 if (--hold_state == 0) {
429                         current_peak = lvl;
430                 }
431         }
432
433         queue_draw ();
434 }
435
436 void
437 FastMeter::clear ()
438 {
439         current_level = 0;
440         current_peak = 0;
441         hold_state = 0;
442         queue_draw ();
443 }