2 Copyright (C) 2003-2006 Paul Davis
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.
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.
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.
26 #include <gdkmm/rectangle.h>
27 #include <gtkmm2ext/fastmeter.h>
28 #include <gtkmm2ext/utils.h>
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; }
35 using namespace Gtkmm2ext;
38 int FastMeter::min_pattern_metric_size = 10;
39 int FastMeter::max_pattern_metric_size = 1024;
41 FastMeter::PatternMap FastMeter::v_pattern_cache;
42 FastMeter::PatternMap FastMeter::h_pattern_cache;
44 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len, int clr0, int clr1, int clr2, int clr3)
51 last_peak_rect.width = 0;
52 last_peak_rect.height = 0;
58 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
63 if (orientation == Vertical) {
67 pattern = request_vertical_meter(dimen, len, clr0, clr1, clr2, clr3);
72 len = 186; // interesting size, eh?
74 pattern = request_horizontal_meter(len, dimen, clr0, clr1, clr2, clr3);
79 if (orientation == Vertical) {
80 pixrect.width = min (pixwidth, (gint) dimen);
81 pixrect.height = pixheight;
83 pixrect.width = pixwidth;
84 pixrect.height = min (pixheight, (gint) dimen);
87 request_width = pixrect.width;
88 request_height= pixrect.height;
91 Cairo::RefPtr<Cairo::Pattern>
92 FastMeter::generate_meter_pattern (
93 int width, int height, int clr0, int clr1, int clr2, int clr3)
95 guint8 r0,g0,b0,r1,g1,b1,r2,g2,b2,r3,g3,b3,a;
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.
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
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);
114 // fake log calculation copied from log_meter.h
115 // actual calculation:
117 // def = (0.0f + 20.0f) * 2.5f + 50f
118 // return def / 115.0f
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);
124 Cairo coordinate space goes downwards as y value goes up, so invert
125 knee-based positions by using (1.0 - y)
129 cairo_pattern_add_color_stop_rgb (pat, 0.0,
130 r3/255.0, g3/255.0, b3/255.0);
133 cairo_pattern_add_color_stop_rgb (pat, 1.0 - (knee/(double)height),
134 r2/255.0, g2/255.0, b2/255.0);
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);
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
144 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
149 Cairo::RefPtr<Cairo::Pattern>
150 FastMeter::request_vertical_meter(
151 int width, int height, int clr0, int clr1, int clr2, int clr3)
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;
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()) {
164 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
165 width, height, clr0, clr1, clr2, clr3);
166 v_pattern_cache[key] = p;
171 Cairo::RefPtr<Cairo::Pattern>
172 FastMeter::request_horizontal_meter(
173 int width, int height, int clr0, int clr1, int clr2, int clr3)
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;
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()) {
186 /* flip height/width so that we get the right pattern */
188 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
189 height, width, clr0, clr1, clr2, clr3);
191 /* rotate to make it horizontal */
194 cairo_matrix_init_rotate (&m, -M_PI/2.0);
195 cairo_pattern_set_matrix (p->cobj(), &m);
197 h_pattern_cache[key] = p;
202 FastMeter::~FastMeter ()
207 FastMeter::set_hold_count (long val)
221 FastMeter::on_size_request (GtkRequisition* req)
223 if (orientation == Vertical) {
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);
229 req->width = request_width;
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);
237 req->height = request_height;
243 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
245 if (orientation == Vertical) {
247 if (alloc.get_width() != request_width) {
248 alloc.set_width (request_width);
251 int h = alloc.get_height();
252 h = max (h, min_pattern_metric_size);
253 h = min (h, max_pattern_metric_size);
255 if (h != alloc.get_height()) {
256 alloc.set_height (h);
259 if (pixheight != h) {
260 pattern = request_vertical_meter (
261 request_width, h, _clr0, _clr1, _clr2, _clr3);
263 pixwidth = request_width;
268 if (alloc.get_height() != request_height) {
269 alloc.set_height(request_height);
272 int w = alloc.get_width();
273 w = max (w, min_pattern_metric_size);
274 w = min (w, max_pattern_metric_size);
276 if (w != alloc.get_width()) {
281 pattern = request_horizontal_meter (
282 w, request_height, _clr0, _clr1, _clr2, _clr3);
283 pixheight = request_height;
288 DrawingArea::on_size_allocate (alloc);
292 FastMeter::on_expose_event (GdkEventExpose* ev)
294 if (orientation == Vertical) {
295 return vertical_expose (ev);
297 return horizontal_expose (ev);
302 FastMeter::vertical_expose (GdkEventExpose* ev)
304 Glib::RefPtr<Gdk::Window> win = get_window ();
306 GdkRectangle intersection;
307 GdkRectangle background;
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);
313 top_of_meter = (gint) floor (pixheight * current_level);
315 /* reset the height & origin of the rect that needs to show the pixbuf
318 pixrect.height = top_of_meter;
319 pixrect.y = pixheight - top_of_meter;
323 background.width = pixrect.width;
324 background.height = pixheight - top_of_meter;
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);
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);
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);
347 cairo_set_source (cr, pattern->cobj());
348 cairo_rectangle (cr, 0, last_peak_rect.y, pixwidth, last_peak_rect.height);
352 last_peak_rect.width = 0;
353 last_peak_rect.height = 0;
362 FastMeter::horizontal_expose (GdkEventExpose* ev)
364 Glib::RefPtr<Gdk::Window> win = get_window ();
366 GdkRectangle intersection;
367 GdkRectangle background;
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);
373 right_of_meter = (gint) floor (pixwidth * current_level);
374 pixrect.width = right_of_meter;
378 background.width = pixwidth - right_of_meter;
379 background.height = pixrect.height;
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);
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)
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);
398 // XXX: peaks don't work properly
400 if (hold_state && intersection.height > 0) {
401 gint x = (gint) floor(pixwidth * current_peak);
403 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
406 3, intersection.height,
407 Gdk::RGB_DITHER_NONE, 0, 0);
417 FastMeter::set (float lvl)
419 float old_level = current_level;
420 float old_peak = current_peak;
424 if (lvl > current_peak) {
426 hold_state = hold_cnt;
429 if (hold_state > 0) {
430 if (--hold_state == 0) {
435 if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
440 Glib::RefPtr<Gdk::Window> win;
442 if ((win = get_window()) == 0) {
447 if (orientation == Vertical) {
448 queue_vertical_redraw (win, old_level);
450 queue_horizontal_redraw (win, old_level);
455 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
459 gint new_top = (gint) floor (pixheight * current_level);
462 rect.width = pixwidth;
463 rect.height = new_top;
464 rect.y = pixheight - new_top;
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
471 X coordinates just make my brain hurt.
473 rect.height = pixrect.y - rect.y;
475 /* it got smaller, compute the difference */
476 /* rect.y becomes old.y (the smaller value) */
478 /* rect.height is the old.y (smaller) minus the new.y (larger)
480 rect.height = pixrect.height - rect.height;
483 GdkRegion* region = 0;
486 if (rect.height != 0) {
488 /* ok, first region to draw ... */
490 region = gdk_region_rectangle (&rect);
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.
499 if (last_peak_rect.width * last_peak_rect.height != 0) {
501 region = gdk_region_new ();
504 gdk_region_union_with_rect (region, &last_peak_rect);
508 gdk_window_invalidate_region (win->gobj(), region, true);
511 gdk_region_destroy(region);
517 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& /*win*/, float /*old_level*/)
519 /* XXX OPTIMIZE (when we have some horizontal meters) */