Prompt the user for confirmation before removing an export format.
[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::PatternMap FastMeter::v_pattern_cache;
42 FastMeter::PatternMap FastMeter::h_pattern_cache;
43
44 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len, int clr0, int clr1, int clr2, int clr3)
45 {
46         orientation = o;
47         hold_cnt = hold;
48         hold_state = 0;
49         current_peak = 0;
50         current_level = 0;
51         last_peak_rect.width = 0;
52         last_peak_rect.height = 0;
53         _clr0 = clr0;
54         _clr1 = clr1;
55         _clr2 = clr2;
56         _clr3 = clr3;
57
58         set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
59
60         pixrect.x = 0;
61         pixrect.y = 0;
62
63         if (orientation == Vertical) {
64                 if (!len) {
65                         len = 250;
66                 }
67                 pattern = request_vertical_meter(dimen, len, clr0, clr1, clr2, clr3);
68                 pixheight = len;
69                 pixwidth = dimen;
70         } else {
71                 if (!len) {
72                         len = 186; // interesting size, eh?
73                 }
74                 pattern = request_horizontal_meter(len, dimen, clr0, clr1, clr2, clr3);
75                 pixheight = dimen;
76                 pixwidth = len;
77         }
78
79         if (orientation == Vertical) {
80                 pixrect.width = min (pixwidth, (gint) dimen);
81                 pixrect.height = pixheight;
82         } else {
83                 pixrect.width = pixwidth;
84                 pixrect.height = min (pixheight, (gint) dimen);
85         }
86
87         request_width = pixrect.width;
88         request_height= pixrect.height;
89 }
90
91 Cairo::RefPtr<Cairo::Pattern>
92 FastMeter::generate_meter_pattern (
93                 int width, int height, int clr0, int clr1, int clr2, int clr3)
94 {
95         guint8 r0,g0,b0,r1,g1,b1,r2,g2,b2,r3,g3,b3,a;
96
97         /*
98           The knee is the hard transition point (e.g. at 0dB where the colors
99           change dramatically to make clipping apparent). Thus there are two
100           gradients in the pattern, the "normal range" and the "clip range", which
101           are separated at the knee point.
102
103           clr0: color at bottom of normal range gradient
104           clr1: color at top of normal range gradient
105           clr2: color at bottom of clip range gradient
106           clr3: color at top of clip range gradient
107         */
108
109         UINT_TO_RGBA (clr0, &r0, &g0, &b0, &a);
110         UINT_TO_RGBA (clr1, &r1, &g1, &b1, &a);
111         UINT_TO_RGBA (clr2, &r2, &g2, &b2, &a);
112         UINT_TO_RGBA (clr3, &r3, &g3, &b3, &a);
113
114         // fake log calculation copied from log_meter.h
115         // actual calculation:
116         // log_meter(0.0f) =
117         //  def = (0.0f + 20.0f) * 2.5f + 50f
118         //  return def / 115.0f
119
120         const int knee = (int)floor((float)height * 100.0f / 115.0f);
121         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, width, height);
122
123         /*
124           Cairo coordinate space goes downwards as y value goes up, so invert
125           knee-based positions by using (1.0 - y)
126         */
127
128         // Clip range top
129         cairo_pattern_add_color_stop_rgb (pat, 0.0,
130                                           r3/255.0, g3/255.0, b3/255.0);
131
132         // Clip range bottom
133         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
134                                           r2/255.0, g2/255.0, b2/255.0);
135
136         // Normal range top (double-stop at knee)
137         cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
138                                           r1/255.0, g1/255.0, b1/255.0);
139
140         // Normal range bottom
141         cairo_pattern_add_color_stop_rgb (pat, 1.0,
142                                           r0/255.0, g0/255.0, b0/255.0); // top
143
144         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
145
146         return p;
147 }
148
149 Cairo::RefPtr<Cairo::Pattern>
150 FastMeter::request_vertical_meter(
151                 int width, int height, int clr0, int clr1, int clr2, int clr3)
152 {
153         if (height < min_pattern_metric_size)
154                 height = min_pattern_metric_size;
155         if (height > max_pattern_metric_size)
156                 height = max_pattern_metric_size;
157
158         const PatternMapKey key (width, height, clr0, clr1, clr2, clr3);
159         PatternMap::iterator i;
160         if ((i = v_pattern_cache.find (key)) != v_pattern_cache.end()) {
161                 return i->second;
162         }
163
164         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
165                 width, height, clr0, clr1, clr2, clr3);
166         v_pattern_cache[key] = p;
167
168         return p;
169 }
170
171 Cairo::RefPtr<Cairo::Pattern>
172 FastMeter::request_horizontal_meter(
173                 int width, int height, int clr0, int clr1, int clr2, int clr3)
174 {
175         if (width < min_pattern_metric_size)
176                 width = min_pattern_metric_size;
177         if (width > max_pattern_metric_size)
178                 width = max_pattern_metric_size;
179
180         const PatternMapKey key (width, height, clr0, clr1, clr2, clr3);
181         PatternMap::iterator i;
182         if ((i = h_pattern_cache.find (key)) != h_pattern_cache.end()) {
183                 return i->second;
184         }
185
186         /* flip height/width so that we get the right pattern */
187
188         Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
189                 height, width, clr0, clr1, clr2, clr3);
190
191         /* rotate to make it horizontal */
192
193         cairo_matrix_t m;
194         cairo_matrix_init_rotate (&m, -M_PI/2.0);
195         cairo_pattern_set_matrix (p->cobj(), &m);
196
197         h_pattern_cache[key] = p;
198
199         return p;
200 }
201
202 FastMeter::~FastMeter ()
203 {
204 }
205
206 void
207 FastMeter::set_hold_count (long val)
208 {
209         if (val < 1) {
210                 val = 1;
211         }
212
213         hold_cnt = val;
214         hold_state = 0;
215         current_peak = 0;
216
217         queue_draw ();
218 }
219
220 void
221 FastMeter::on_size_request (GtkRequisition* req)
222 {
223         if (orientation == Vertical) {
224
225                 req->height = request_height;
226                 req->height = max(req->height, min_pattern_metric_size);
227                 req->height = min(req->height, max_pattern_metric_size);
228
229                 req->width  = request_width;
230
231         } else {
232
233                 req->width  = request_width;
234                 req->width  = max(req->width,  min_pattern_metric_size);
235                 req->width  = min(req->width,  max_pattern_metric_size);
236
237                 req->height = request_height;
238         }
239
240 }
241
242 void
243 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
244 {
245         if (orientation == Vertical) {
246
247                 if (alloc.get_width() != request_width) {
248                         alloc.set_width (request_width);
249                 }
250
251                 int h = alloc.get_height();
252                 h = max (h, min_pattern_metric_size);
253                 h = min (h, max_pattern_metric_size);
254
255                 if (h != alloc.get_height()) {
256                         alloc.set_height (h);
257                 }
258
259                 if (pixheight != h) {
260                         pattern = request_vertical_meter (
261                                 request_width, h, _clr0, _clr1, _clr2, _clr3);
262                         pixheight = h;
263                         pixwidth  = request_width;
264                 }
265
266         } else {
267
268                 if (alloc.get_height() != request_height) {
269                         alloc.set_height(request_height);
270                 }
271
272                 int w = alloc.get_width();
273                 w = max (w, min_pattern_metric_size);
274                 w = min (w, max_pattern_metric_size);
275
276                 if (w != alloc.get_width()) {
277                         alloc.set_width (w);
278                 }
279
280                 if (pixwidth != w) {
281                         pattern = request_horizontal_meter (
282                                 w, request_height, _clr0, _clr1, _clr2, _clr3);
283                         pixheight = request_height;
284                         pixwidth  = w;
285                 }
286         }
287
288         DrawingArea::on_size_allocate (alloc);
289 }
290
291 bool
292 FastMeter::on_expose_event (GdkEventExpose* ev)
293 {
294         if (orientation == Vertical) {
295                 return vertical_expose (ev);
296         } else {
297                 return horizontal_expose (ev);
298         }
299 }
300
301 bool
302 FastMeter::vertical_expose (GdkEventExpose* ev)
303 {
304         Glib::RefPtr<Gdk::Window> win = get_window ();
305         gint top_of_meter;
306         GdkRectangle intersection;
307         GdkRectangle background;
308
309         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
310         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
311         cairo_clip (cr);
312
313         top_of_meter = (gint) floor (pixheight * current_level);
314
315         /* reset the height & origin of the rect that needs to show the pixbuf
316          */
317
318         pixrect.height = top_of_meter;
319         pixrect.y = pixheight - top_of_meter;
320
321         background.x = 0;
322         background.y = 0;
323         background.width = pixrect.width;
324         background.height = pixheight - top_of_meter;
325
326         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
327                 cairo_set_source_rgb (cr, 0, 0, 0); // black
328                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
329                 cairo_fill (cr);
330         }
331
332         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
333                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
334                 cairo_set_source (cr, pattern->cobj());
335                 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
336                 cairo_fill (cr);
337         }
338
339         // draw peak bar
340
341         if (hold_state) {
342                 last_peak_rect.x = 0;
343                 last_peak_rect.width = pixwidth;
344                 last_peak_rect.y = pixheight - (gint) floor (pixheight * current_peak);
345                 last_peak_rect.height = min(3, pixheight - last_peak_rect.y);
346
347                 cairo_set_source (cr, pattern->cobj());
348                 cairo_rectangle (cr, 0, last_peak_rect.y, pixwidth, last_peak_rect.height);
349                 cairo_fill (cr);
350
351         } else {
352                 last_peak_rect.width = 0;
353                 last_peak_rect.height = 0;
354         }
355
356         cairo_destroy (cr);
357
358         return TRUE;
359 }
360
361 bool
362 FastMeter::horizontal_expose (GdkEventExpose* ev)
363 {
364         Glib::RefPtr<Gdk::Window> win = get_window ();
365         gint right_of_meter;
366         GdkRectangle intersection;
367         GdkRectangle background;
368
369         cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
370         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
371         cairo_clip (cr);
372
373         right_of_meter = (gint) floor (pixwidth * current_level);
374         pixrect.width = right_of_meter;
375
376         background.x = 0;
377         background.y = 0;
378         background.width  = pixwidth - right_of_meter;
379         background.height = pixrect.height;
380
381         if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
382                 cairo_set_source_rgb (cr, 0, 0, 0); // black
383                 cairo_rectangle (cr, intersection.x + right_of_meter, intersection.y, intersection.width, intersection.height);
384                 cairo_fill (cr);
385         }
386
387         if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
388                 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
389                 cairo_matrix_t m;
390                 cairo_matrix_init_translate (&m, -intersection.x, -intersection.y);
391                 cairo_pattern_set_matrix (pattern->cobj(), &m);
392                 cairo_set_source (cr, pattern->cobj());
393                 cairo_rectangle (cr, intersection.x, intersection.y, pixrect.width, intersection.height);
394                 cairo_fill (cr);
395         }
396
397         // draw peak bar
398         // XXX: peaks don't work properly
399         /*
400         if (hold_state && intersection.height > 0) {
401                 gint x = (gint) floor(pixwidth * current_peak);
402
403                 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
404                                            x, intersection.y,
405                                            x, intersection.y,
406                                            3, intersection.height,
407                                            Gdk::RGB_DITHER_NONE, 0, 0);
408         }
409         */
410
411         cairo_destroy (cr);
412
413         return true;
414 }
415
416 void
417 FastMeter::set (float lvl)
418 {
419         float old_level = current_level;
420         float old_peak = current_peak;
421
422         current_level = lvl;
423
424         if (lvl > current_peak) {
425                 current_peak = lvl;
426                 hold_state = hold_cnt;
427         }
428
429         if (hold_state > 0) {
430                 if (--hold_state == 0) {
431                         current_peak = lvl;
432                 }
433         }
434
435         if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
436                 return;
437         }
438
439
440         Glib::RefPtr<Gdk::Window> win;
441
442         if ((win = get_window()) == 0) {
443                 queue_draw ();
444                 return;
445         }
446
447         if (orientation == Vertical) {
448                 queue_vertical_redraw (win, old_level);
449         } else {
450                 queue_horizontal_redraw (win, old_level);
451         }
452 }
453
454 void
455 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
456 {
457         GdkRectangle rect;
458
459         gint new_top = (gint) floor (pixheight * current_level);
460
461         rect.x = 0;
462         rect.width = pixwidth;
463         rect.height = new_top;
464         rect.y = pixheight - new_top;
465
466         if (current_level > old_level) {
467                 /* colored/pixbuf got larger, just draw the new section */
468                 /* rect.y stays where it is because of X coordinates */
469                 /* height of invalidated area is between new.y (smaller) and old.y
470                    (larger).
471                    X coordinates just make my brain hurt.
472                 */
473                 rect.height = pixrect.y - rect.y;
474         } else {
475                 /* it got smaller, compute the difference */
476                 /* rect.y becomes old.y (the smaller value) */
477                 rect.y = pixrect.y;
478                 /* rect.height is the old.y (smaller) minus the new.y (larger)
479                 */
480                 rect.height = pixrect.height - rect.height;
481         }
482
483         GdkRegion* region = 0;
484         bool queue = false;
485
486         if (rect.height != 0) {
487
488                 /* ok, first region to draw ... */
489
490                 region = gdk_region_rectangle (&rect);
491                 queue = true;
492         }
493
494         /* redraw the last place where the last peak hold bar was;
495            the next expose will draw the new one whether its part of
496            expose region or not.
497         */
498
499         if (last_peak_rect.width * last_peak_rect.height != 0) {
500                 if (!queue) {
501                         region = gdk_region_new ();
502                         queue = true;
503                 }
504                 gdk_region_union_with_rect (region, &last_peak_rect);
505         }
506
507         if (queue) {
508                 gdk_window_invalidate_region (win->gobj(), region, true);
509         }
510         if (region) {
511                 gdk_region_destroy(region);
512                 region = 0;
513         }
514 }
515
516 void
517 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& /*win*/, float /*old_level*/)
518 {
519         /* XXX OPTIMIZE (when we have some horizontal meters) */
520         queue_draw ();
521 }
522
523 void
524 FastMeter::clear ()
525 {
526         current_level = 0;
527         current_peak = 0;
528         hold_state = 0;
529         queue_draw ();
530 }