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.
24 #include <gdkmm/rectangle.h>
25 #include <gtkmm2ext/fastmeter.h>
26 #include <gtkmm2ext/utils.h>
27 #include <gtkmm/style.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
;
39 int FastMeter::min_v_pixbuf_size
= 10;
40 int FastMeter::max_v_pixbuf_size
= 1024;
41 Glib::RefPtr
<Gdk::Pixbuf
>* FastMeter::v_pixbuf_cache
= 0;
43 int FastMeter::min_h_pixbuf_size
= 10;
44 int FastMeter::max_h_pixbuf_size
= 1024;
45 Glib::RefPtr
<Gdk::Pixbuf
>* FastMeter::h_pixbuf_cache
= 0;
47 int FastMeter::_clr0
= 0;
48 int FastMeter::_clr1
= 0;
49 int FastMeter::_clr2
= 0;
50 int FastMeter::_clr3
= 0;
52 FastMeter::FastMeter (long hold
, unsigned long dimen
, Orientation o
, int len
, int clr0
, int clr1
, int clr2
, int clr3
)
59 last_peak_rect
.width
= 0;
60 last_peak_rect
.height
= 0;
66 set_events (BUTTON_PRESS_MASK
|BUTTON_RELEASE_MASK
);
71 if (orientation
== Vertical
) {
74 pixbuf
= request_vertical_meter(dimen
, len
);
78 pixbuf
= request_horizontal_meter(len
, dimen
);
81 pixheight
= pixbuf
->get_height();
82 pixwidth
= pixbuf
->get_width();
84 if (orientation
== Vertical
) {
85 pixrect
.width
= min (pixwidth
, (gint
) dimen
);
86 pixrect
.height
= pixheight
;
88 pixrect
.width
= pixwidth
;
89 pixrect
.height
= min (pixheight
, (gint
) dimen
);
92 request_width
= pixrect
.width
;
93 request_height
= pixrect
.height
;
96 Glib::RefPtr
<Gdk::Pixbuf
> FastMeter::request_vertical_meter(int width
, int height
)
98 if (height
< min_v_pixbuf_size
)
99 height
= min_v_pixbuf_size
;
100 if (height
> max_v_pixbuf_size
)
101 height
= max_v_pixbuf_size
;
103 //int index = height - 1;
105 //if (v_pixbuf_cache == 0) {
106 // v_pixbuf_cache = (Glib::RefPtr<Gdk::Pixbuf>*) malloc(sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_v_pixbuf_size);
107 // memset(v_pixbuf_cache,0,sizeof(Glib::RefPtr<Gdk::Pixbuf>) * max_v_pixbuf_size);
109 Glib::RefPtr
<Gdk::Pixbuf
> ret
;// = v_pixbuf_cache[index];
115 data
= (guint8
*) malloc(width
*height
* 3);
117 guint8 r
,g
,b
,r0
,g0
,b0
,r1
,g1
,b1
,r2
,g2
,b2
,r3
,g3
,b3
,a
;
119 UINT_TO_RGBA (_clr0
, &r0
, &g0
, &b0
, &a
);
120 UINT_TO_RGBA (_clr1
, &r1
, &g1
, &b1
, &a
);
121 UINT_TO_RGBA (_clr2
, &r2
, &g2
, &b2
, &a
);
122 UINT_TO_RGBA (_clr3
, &r3
, &g3
, &b3
, &a
);
123 // fake log calculation copied from log_meter.h
124 // actual calculation:
126 // def = (0.0f + 20.0f) * 2.5f + 50f
127 // return def / 115.0f
128 int knee
= (int)floor((float)height
* 100.0f
/ 115.0f
);
131 for (y
= 0; y
< knee
/2; y
++) {
133 r
= (guint8
)floor((float)abs(r1
- r0
) * (float)y
/ (float)(knee
/2));
134 (r0
>= r1
) ? r
= r0
- r
: r
+= r0
;
136 g
= (guint8
)floor((float)abs(g1
- g0
) * (float)y
/ (float)(knee
/2));
137 (g0
>= g1
) ? g
= g0
- g
: g
+= g0
;
139 b
= (guint8
)floor((float)abs(b1
- b0
) * (float)y
/ (float)(knee
/2));
140 (b0
>= b1
) ? b
= b0
- b
: b
+= b0
;
142 for (int x
= 0; x
< width
; x
++) {
143 data
[ (x
+(height
-y
-1)*width
) * 3 + 0 ] = r
;
144 data
[ (x
+(height
-y
-1)*width
) * 3 + 1 ] = g
;
145 data
[ (x
+(height
-y
-1)*width
) * 3 + 2 ] = b
;
149 int offset
= knee
- y
;
150 for (int i
=0; i
< offset
; i
++,y
++) {
152 r
= (guint8
)floor((float)abs(r2
- r1
) * (float)i
/ (float)offset
);
153 (r1
>= r2
) ? r
= r1
- r
: r
+= r1
;
155 g
= (guint8
)floor((float)abs(g2
- g1
) * (float)i
/ (float)offset
);
156 (g1
>= g2
) ? g
= g1
- g
: g
+= g1
;
158 b
= (guint8
)floor((float)abs(b2
- b1
) * (float)i
/ (float)offset
);
159 (b1
>= b2
) ? b
= b1
- b
: b
+= b1
;
161 for (int x
= 0; x
< width
; x
++) {
162 data
[ (x
+(height
-y
-1)*width
) * 3 + 0 ] = r
;
163 data
[ (x
+(height
-y
-1)*width
) * 3 + 1 ] = g
;
164 data
[ (x
+(height
-y
-1)*width
) * 3 + 2 ] = b
;
168 for (; y
< height
; y
++) {
169 for (int x
= 0; x
< width
; x
++) {
170 data
[ (x
+(height
-y
-1)*width
) * 3 + 0 ] = r3
;
171 data
[ (x
+(height
-y
-1)*width
) * 3 + 1 ] = g3
;
172 data
[ (x
+(height
-y
-1)*width
) * 3 + 2 ] = b3
;
176 ret
= Pixbuf::create_from_data(data
, COLORSPACE_RGB
, false, 8, width
, height
, width
* 3);
177 //v_pixbuf_cache[index] = ret;
182 Glib::RefPtr
<Gdk::Pixbuf
> FastMeter::request_horizontal_meter(int width
, int height
)
184 if (width
< min_h_pixbuf_size
)
185 width
= min_h_pixbuf_size
;
186 if (width
> max_h_pixbuf_size
)
187 width
= max_h_pixbuf_size
;
189 int index
= width
- 1;
191 if (h_pixbuf_cache
== 0) {
192 h_pixbuf_cache
= (Glib::RefPtr
<Gdk::Pixbuf
>*) malloc(sizeof(Glib::RefPtr
<Gdk::Pixbuf
>) * max_h_pixbuf_size
);
193 memset(h_pixbuf_cache
,0,sizeof(Glib::RefPtr
<Gdk::Pixbuf
>) * max_h_pixbuf_size
);
195 Glib::RefPtr
<Gdk::Pixbuf
> ret
= h_pixbuf_cache
[index
];
201 data
= (guint8
*) malloc(width
*height
* 3);
208 // fake log calculation copied from log_meter.h
209 // actual calculation:
211 // def = (0.0f + 20.0f) * 2.5f + 50f
212 // return def / 115.0f
213 int knee
= (int)floor((float)width
* 100.0f
/ 115.0f
);
217 for (x
= 0; x
< knee
/ 2; x
++) {
219 r
= (guint8
)floor(255.0 * (float)x
/(float)(knee
/ 2));
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
;
228 for (; x
< knee
; x
++) {
230 g
= 255 - (guint8
)floor(170.0 * (float)(x
- knee
/ 2)/(float)(knee
/ 2));
232 for (int y
= 0; y
< height
; y
++) {
233 data
[ (x
+(height
-y
-1)*width
) * 3 + 0 ] = r
;
234 data
[ (x
+(height
-y
-1)*width
) * 3 + 1 ] = g
;
235 data
[ (x
+(height
-y
-1)*width
) * 3 + 2 ] = b
;
242 for (; x
< width
; x
++) {
243 for (int y
= 0; y
< height
; y
++) {
244 data
[ (x
+(height
-y
-1)*width
) * 3 + 0 ] = r
;
245 data
[ (x
+(height
-y
-1)*width
) * 3 + 1 ] = g
;
246 data
[ (x
+(height
-y
-1)*width
) * 3 + 2 ] = b
;
250 ret
= Pixbuf::create_from_data(data
, COLORSPACE_RGB
, false, 8, width
, height
, width
* 3);
251 h_pixbuf_cache
[index
] = ret
;
256 FastMeter::~FastMeter ()
261 FastMeter::set_hold_count (long val
)
275 FastMeter::on_size_request (GtkRequisition
* req
)
277 if (orientation
== Vertical
) {
279 req
->height
= request_height
;
280 req
->height
= max(req
->height
, min_v_pixbuf_size
);
281 req
->height
= min(req
->height
, max_v_pixbuf_size
);
283 req
->width
= request_width
;
287 req
->width
= request_width
;
288 req
->width
= max(req
->width
, min_h_pixbuf_size
);
289 req
->width
= min(req
->width
, max_h_pixbuf_size
);
291 req
->height
= request_height
;
297 FastMeter::on_size_allocate (Gtk::Allocation
&alloc
)
299 if (orientation
== Vertical
) {
301 if (alloc
.get_width() != request_width
) {
302 alloc
.set_width (request_width
);
305 int h
= alloc
.get_height();
306 h
= max(h
, min_v_pixbuf_size
);
307 h
= min(h
, max_v_pixbuf_size
);
309 if ( h
!= alloc
.get_height())
312 if (pixheight
!= h
) {
313 pixbuf
= request_vertical_meter(request_width
, h
);
318 if (alloc
.get_height() != request_height
) {
319 alloc
.set_height(request_height
);
322 int w
= alloc
.get_width();
323 w
= max(w
, min_h_pixbuf_size
);
324 w
= min(w
, max_h_pixbuf_size
);
326 if ( w
!= alloc
.get_width())
330 pixbuf
= request_horizontal_meter(w
, request_height
);
334 pixheight
= pixbuf
->get_height();
335 pixwidth
= pixbuf
->get_width();
337 DrawingArea::on_size_allocate(alloc
);
341 FastMeter::on_expose_event (GdkEventExpose
* ev
)
343 if (orientation
== Vertical
) {
344 return vertical_expose (ev
);
346 return horizontal_expose (ev
);
351 FastMeter::vertical_expose (GdkEventExpose
* ev
)
354 GdkRectangle intersection
;
355 GdkRectangle background
;
357 top_of_meter
= (gint
) floor (pixheight
* current_level
);
359 /* reset the height & origin of the rect that needs to show the pixbuf
362 pixrect
.height
= top_of_meter
;
363 pixrect
.y
= pixheight
- top_of_meter
;
367 background
.width
= pixrect
.width
;
368 background
.height
= pixheight
- top_of_meter
;
370 if (gdk_rectangle_intersect (&background
, &ev
->area
, &intersection
)) {
371 get_window()->draw_rectangle (get_style()->get_black_gc(), true,
372 intersection
.x
, intersection
.y
,
373 intersection
.width
, intersection
.height
);
376 if (gdk_rectangle_intersect (&pixrect
, &ev
->area
, &intersection
)) {
377 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
378 get_window()->draw_pixbuf(get_style()->get_fg_gc(get_state()), pixbuf
,
379 intersection
.x
, intersection
.y
,
380 intersection
.x
, intersection
.y
,
381 intersection
.width
, intersection
.height
,
382 Gdk::RGB_DITHER_NONE
, 0, 0);
388 last_peak_rect
.x
= 0;
389 last_peak_rect
.width
= pixwidth
;
390 last_peak_rect
.y
= pixheight
- (gint
) floor (pixheight
* current_peak
);
391 last_peak_rect
.height
= min(3, pixheight
- last_peak_rect
.y
);
393 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf
,
396 pixwidth
, last_peak_rect
.height
,
397 Gdk::RGB_DITHER_NONE
, 0, 0);
399 last_peak_rect
.width
= 0;
400 last_peak_rect
.height
= 0;
407 FastMeter::horizontal_expose (GdkEventExpose
* ev
)
410 GdkRectangle intersection
;
411 GdkRectangle background
;
413 right_of_meter
= (gint
) floor (pixwidth
* current_level
);
414 pixrect
.width
= right_of_meter
;
418 background
.width
= pixwidth
- right_of_meter
;
419 background
.height
= pixrect
.height
;
421 if (gdk_rectangle_intersect (&background
, &ev
->area
, &intersection
)) {
422 get_window()->draw_rectangle (get_style()->get_black_gc(), true,
423 intersection
.x
+ right_of_meter
, intersection
.y
,
424 intersection
.width
, intersection
.height
);
427 if (gdk_rectangle_intersect (&pixrect
, &ev
->area
, &intersection
)) {
428 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
429 get_window()->draw_pixbuf(get_style()->get_fg_gc(get_state()), pixbuf
,
430 intersection
.x
, intersection
.y
,
431 intersection
.x
, intersection
.y
,
432 pixrect
.width
, intersection
.height
,
433 Gdk::RGB_DITHER_NONE
, 0, 0);
437 // XXX: peaks don't work properly
439 if (hold_state && intersection.height > 0) {
440 gint x = (gint) floor(pixwidth * current_peak);
442 get_window()->draw_pixbuf (get_style()->get_fg_gc(get_state()), pixbuf,
445 3, intersection.height,
446 Gdk::RGB_DITHER_NONE, 0, 0);
454 FastMeter::set (float lvl
)
456 float old_level
= current_level
;
457 float old_peak
= current_peak
;
461 if (lvl
> current_peak
) {
463 hold_state
= hold_cnt
;
466 if (hold_state
> 0) {
467 if (--hold_state
== 0) {
472 if (current_level
== old_level
&& current_peak
== old_peak
&& hold_state
== 0) {
477 Glib::RefPtr
<Gdk::Window
> win
;
479 if ((win
= get_window()) == 0) {
484 if (orientation
== Vertical
) {
485 queue_vertical_redraw (win
, old_level
);
487 queue_horizontal_redraw (win
, old_level
);
492 FastMeter::queue_vertical_redraw (const Glib::RefPtr
<Gdk::Window
>& win
, float old_level
)
496 gint new_top
= (gint
) floor (pixheight
* current_level
);
499 rect
.width
= pixwidth
;
500 rect
.height
= new_top
;
501 rect
.y
= pixheight
- new_top
;
503 if (current_level
> old_level
) {
504 /* colored/pixbuf got larger, just draw the new section */
505 /* rect.y stays where it is because of X coordinates */
506 /* height of invalidated area is between new.y (smaller) and old.y
508 X coordinates just make my brain hurt.
510 rect
.height
= pixrect
.y
- rect
.y
;
512 /* it got smaller, compute the difference */
513 /* rect.y becomes old.y (the smaller value) */
515 /* rect.height is the old.y (smaller) minus the new.y (larger)
517 rect
.height
= pixrect
.height
- rect
.height
;
520 GdkRegion
* region
= 0;
523 if (rect
.height
!= 0) {
525 /* ok, first region to draw ... */
527 region
= gdk_region_rectangle (&rect
);
531 /* redraw the last place where the last peak hold bar was;
532 the next expose will draw the new one whether its part of
533 expose region or not.
536 if (last_peak_rect
.width
* last_peak_rect
.height
!= 0) {
538 region
= gdk_region_new ();
541 gdk_region_union_with_rect (region
, &last_peak_rect
);
545 gdk_window_invalidate_region (win
->gobj(), region
, true);
548 gdk_region_destroy(region
);
554 FastMeter::queue_horizontal_redraw (const Glib::RefPtr
<Gdk::Window
>& /*win*/, float /*old_level*/)
556 /* XXX OPTIMIZE (when we have some horizontal meters) */