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;
97 /* clr0: color at top of the meter
99 2: color half-way between bottom and knee
100 3: color at the bottom of the meter
103 UINT_TO_RGBA (clr0, &r0, &g0, &b0, &a);
104 UINT_TO_RGBA (clr1, &r1, &g1, &b1, &a);
105 UINT_TO_RGBA (clr2, &r2, &g2, &b2, &a);
106 UINT_TO_RGBA (clr3, &r3, &g3, &b3, &a);
108 // fake log calculation copied from log_meter.h
109 // actual calculation:
111 // def = (0.0f + 20.0f) * 2.5f + 50f
112 // return def / 115.0f
114 const int knee = (int)floor((float)height * 100.0f / 115.0f);
115 cairo_pattern_t* _p = cairo_pattern_create_linear (0.0, 0.0, width, height);
117 /* cairo coordinate space goes downwards as y value goes up, so invert
118 * knee-based positions by using (1.0 - y)
120 * also, double-stop the knee point, so that we get a hard transition
123 cairo_pattern_add_color_stop_rgb (_p, 0.0, r3/255.0, g3/255.0, b3/255.0); // bottom
124 cairo_pattern_add_color_stop_rgb (_p, 1.0 - (knee/(2.0 * height)), r2/255.0, g2/255.0, b2/255.0); // mid-point to knee
125 cairo_pattern_add_color_stop_rgb (_p, 1.0 - (knee/(double)height), r0/255.0, g0/255.0, b0/255.0); // knee
126 cairo_pattern_add_color_stop_rgb (_p, 1.0 - (knee/(double)height), r1/255.0, g1/255.0, b1/255.0); // double-stop @ knee
127 cairo_pattern_add_color_stop_rgb (_p, 1.0, r0/255.0, g0/255.0, b0/255.0); // top
129 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (_p, false));
134 Cairo::RefPtr<Cairo::Pattern>
135 FastMeter::request_vertical_meter(
136 int width, int height, int clr0, int clr1, int clr2, int clr3)
138 if (height < min_pattern_metric_size)
139 height = min_pattern_metric_size;
140 if (height > max_pattern_metric_size)
141 height = max_pattern_metric_size;
143 const PatternMapKey key (width, height, clr0, clr1, clr2, clr3);
144 PatternMap::iterator i;
145 if ((i = v_pattern_cache.find (key)) != v_pattern_cache.end()) {
149 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
150 width, height, clr0, clr1, clr2, clr3);
151 v_pattern_cache[key] = p;
156 Cairo::RefPtr<Cairo::Pattern>
157 FastMeter::request_horizontal_meter(
158 int width, int height, int clr0, int clr1, int clr2, int clr3)
160 if (width < min_pattern_metric_size)
161 width = min_pattern_metric_size;
162 if (width > max_pattern_metric_size)
163 width = max_pattern_metric_size;
165 const PatternMapKey key (width, height, clr0, clr1, clr2, clr3);
166 PatternMap::iterator i;
167 if ((i = h_pattern_cache.find (key)) != h_pattern_cache.end()) {
171 /* flip height/width so that we get the right pattern */
173 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
174 height, width, clr0, clr1, clr2, clr3);
176 /* rotate to make it horizontal */
179 cairo_matrix_init_rotate (&m, -M_PI/2.0);
180 cairo_pattern_set_matrix (p->cobj(), &m);
182 h_pattern_cache[key] = p;
187 FastMeter::~FastMeter ()
192 FastMeter::set_hold_count (long val)
206 FastMeter::on_size_request (GtkRequisition* req)
208 if (orientation == Vertical) {
210 req->height = request_height;
211 req->height = max(req->height, min_pattern_metric_size);
212 req->height = min(req->height, max_pattern_metric_size);
214 req->width = request_width;
218 req->width = request_width;
219 req->width = max(req->width, min_pattern_metric_size);
220 req->width = min(req->width, max_pattern_metric_size);
222 req->height = request_height;
228 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
230 if (orientation == Vertical) {
232 if (alloc.get_width() != request_width) {
233 alloc.set_width (request_width);
236 int h = alloc.get_height();
237 h = max (h, min_pattern_metric_size);
238 h = min (h, max_pattern_metric_size);
240 if (h != alloc.get_height()) {
241 alloc.set_height (h);
244 if (pixheight != h) {
245 pattern = request_vertical_meter (
246 request_width, h, _clr0, _clr1, _clr2, _clr3);
248 pixwidth = request_width;
253 if (alloc.get_height() != request_height) {
254 alloc.set_height(request_height);
257 int w = alloc.get_width();
258 w = max (w, min_pattern_metric_size);
259 w = min (w, max_pattern_metric_size);
261 if (w != alloc.get_width()) {
266 pattern = request_horizontal_meter (
267 w, request_height, _clr0, _clr1, _clr2, _clr3);
268 pixheight = request_height;
273 DrawingArea::on_size_allocate (alloc);
277 FastMeter::on_expose_event (GdkEventExpose* ev)
279 if (orientation == Vertical) {
280 return vertical_expose (ev);
282 return horizontal_expose (ev);
287 FastMeter::vertical_expose (GdkEventExpose* ev)
289 Glib::RefPtr<Gdk::Window> win = get_window ();
291 GdkRectangle intersection;
292 GdkRectangle background;
294 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
295 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
298 top_of_meter = (gint) floor (pixheight * current_level);
300 /* reset the height & origin of the rect that needs to show the pixbuf
303 pixrect.height = top_of_meter;
304 pixrect.y = pixheight - top_of_meter;
308 background.width = pixrect.width;
309 background.height = pixheight - top_of_meter;
311 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
312 cairo_set_source_rgb (cr, 0, 0, 0); // black
313 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
317 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
318 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
319 cairo_set_source (cr, pattern->cobj());
320 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
327 last_peak_rect.x = 0;
328 last_peak_rect.width = pixwidth;
329 last_peak_rect.y = pixheight - (gint) floor (pixheight * current_peak);
330 last_peak_rect.height = min(3, pixheight - last_peak_rect.y);
332 cairo_set_source (cr, pattern->cobj());
333 cairo_rectangle (cr, 0, last_peak_rect.y, pixwidth, last_peak_rect.height);
337 last_peak_rect.width = 0;
338 last_peak_rect.height = 0;
347 FastMeter::horizontal_expose (GdkEventExpose* ev)
349 Glib::RefPtr<Gdk::Window> win = get_window ();
351 GdkRectangle intersection;
352 GdkRectangle background;
354 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
355 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
358 right_of_meter = (gint) floor (pixwidth * current_level);
359 pixrect.width = right_of_meter;
363 background.width = pixwidth - right_of_meter;
364 background.height = pixrect.height;
366 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
367 cairo_set_source_rgb (cr, 0, 0, 0); // black
368 cairo_rectangle (cr, intersection.x + right_of_meter, intersection.y, intersection.width, intersection.height);
372 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
373 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
375 cairo_matrix_init_translate (&m, -intersection.x, -intersection.y);
376 cairo_pattern_set_matrix (pattern->cobj(), &m);
377 cairo_set_source (cr, pattern->cobj());
378 cairo_rectangle (cr, intersection.x, intersection.y, pixrect.width, intersection.height);
383 // XXX: peaks don't work properly
385 if (hold_state && intersection.height > 0) {
386 gint x = (gint) floor(pixwidth * current_peak);
388 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
391 3, intersection.height,
392 Gdk::RGB_DITHER_NONE, 0, 0);
402 FastMeter::set (float lvl)
404 float old_level = current_level;
405 float old_peak = current_peak;
409 if (lvl > current_peak) {
411 hold_state = hold_cnt;
414 if (hold_state > 0) {
415 if (--hold_state == 0) {
420 if (current_level == old_level && current_peak == old_peak && hold_state == 0) {
425 Glib::RefPtr<Gdk::Window> win;
427 if ((win = get_window()) == 0) {
432 if (orientation == Vertical) {
433 queue_vertical_redraw (win, old_level);
435 queue_horizontal_redraw (win, old_level);
440 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
444 gint new_top = (gint) floor (pixheight * current_level);
447 rect.width = pixwidth;
448 rect.height = new_top;
449 rect.y = pixheight - new_top;
451 if (current_level > old_level) {
452 /* colored/pixbuf got larger, just draw the new section */
453 /* rect.y stays where it is because of X coordinates */
454 /* height of invalidated area is between new.y (smaller) and old.y
456 X coordinates just make my brain hurt.
458 rect.height = pixrect.y - rect.y;
460 /* it got smaller, compute the difference */
461 /* rect.y becomes old.y (the smaller value) */
463 /* rect.height is the old.y (smaller) minus the new.y (larger)
465 rect.height = pixrect.height - rect.height;
468 GdkRegion* region = 0;
471 if (rect.height != 0) {
473 /* ok, first region to draw ... */
475 region = gdk_region_rectangle (&rect);
479 /* redraw the last place where the last peak hold bar was;
480 the next expose will draw the new one whether its part of
481 expose region or not.
484 if (last_peak_rect.width * last_peak_rect.height != 0) {
486 region = gdk_region_new ();
489 gdk_region_union_with_rect (region, &last_peak_rect);
493 gdk_window_invalidate_region (win->gobj(), region, true);
496 gdk_region_destroy(region);
502 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& /*win*/, float /*old_level*/)
504 /* XXX OPTIMIZE (when we have some horizontal meters) */