2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007 Krzysztof Foltman
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include <calf/custom_ctl.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <cairo/cairo.h>
28 I don't really know how to do it, or if it can be done this way.
29 struct calf_ui_type_module
35 module = g_type_module_new();
36 g_type_module_set_name(module, "calf_custom_ctl");
37 g_type_module_use(module);
39 ~calf_ui_type_module()
41 g_type_module_unuse(module);
45 static calf_ui_type_module type_module;
49 calf_line_graph_copy_cache_to_window( CalfLineGraph
*lg
, cairo_t
*c
)
52 cairo_set_source_surface( c
, lg
->cache_surface
, 0,0 );
58 calf_line_graph_copy_window_to_cache( CalfLineGraph
*lg
, cairo_t
*c
)
60 cairo_t
*cache_cr
= cairo_create( lg
->cache_surface
);
61 cairo_surface_t
*window_surface
= cairo_get_target( c
);
62 cairo_set_source_surface( cache_cr
, window_surface
, 0,0 );
63 cairo_paint( cache_cr
);
64 cairo_destroy( cache_cr
);
68 calf_line_graph_draw_grid( cairo_t
*c
, std::string
&legend
, bool vertical
, float pos
, int phase
, int sx
, int sy
)
71 cairo_text_extents_t tx
;
73 cairo_text_extents(c
, legend
.c_str(), &tx
);
76 float x
= floor(ox
+ pos
* sx
) + 0.5;
79 cairo_move_to(c
, x
, oy
);
80 cairo_line_to(c
, x
, oy
+ sy
);
83 if (phase
== 2 && !legend
.empty()) {
85 cairo_set_source_rgba(c
, 1.0, 1.0, 1.0, 0.75);
86 cairo_move_to(c
, x
- (tx
.x_bearing
+ tx
.width
/ 2.0), oy
+ sy
- 2);
87 cairo_show_text(c
, legend
.c_str());
92 float y
= floor(oy
+ sy
/ 2 - (sy
/ 2 - 1) * pos
) + 0.5;
95 cairo_move_to(c
, ox
, y
);
96 cairo_line_to(c
, ox
+ sx
, y
);
99 if (phase
== 2 && !legend
.empty()) {
100 cairo_set_source_rgba(c
, 1.0, 1.0, 1.0, 0.75);
101 cairo_move_to(c
, ox
+ sx
- 2 - tx
.width
, y
+ tx
.height
/2 - 1);
102 cairo_show_text(c
, legend
.c_str());
108 calf_line_graph_draw_graph( cairo_t
*c
, float *data
, int sx
, int sy
)
112 for (int i
= 0; i
< 2 * sx
; i
++)
114 int y
= (int)(oy
+ sy
/ 2 - (sy
/ 2 - 1) * data
[i
]);
115 //if (y < oy) y = oy;
116 //if (y >= oy + sy) y = oy + sy - 1;
118 cairo_line_to(c
, ox
+ i
* 0.5, y
);
120 cairo_move_to(c
, ox
, y
);
126 calf_line_graph_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
128 g_assert(CALF_IS_LINE_GRAPH(widget
));
130 CalfLineGraph
*lg
= CALF_LINE_GRAPH(widget
);
131 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
133 int sx
= widget
->allocation
.width
- 2, sy
= widget
->allocation
.height
- 2;
135 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
137 GdkColor sc
= { 0, 0, 0, 0 };
139 bool cache_dirty
= 0;
143 if( lg
->cache_surface
== NULL
) {
144 // looks like its either first call or the widget has been resized.
145 // create the cache_surface.
146 cairo_surface_t
*window_surface
= cairo_get_target( c
);
147 lg
->cache_surface
= cairo_surface_create_similar( window_surface
,
149 widget
->allocation
.width
,
150 widget
->allocation
.height
);
151 //cairo_set_source_surface( cache_cr, window_surface, 0,0 );
152 //cairo_paint( cache_cr );
157 cairo_select_font_face(c
, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
158 cairo_set_font_size(c
, 9);
160 gdk_cairo_set_source_color(c
, &sc
);
161 cairo_rectangle(c
, ox
, oy
, sx
, sy
);
170 bool vertical
= false;
172 float *data
= new float[2 * sx
];
173 GdkColor sc2
= { 0, 0, 65535, 0 };
176 GdkColor sc3
= { 0, 32767, 65535, 0 };
178 int graph_n
, grid_n
, dot_n
, grid_n_save
;
180 int cache_graph_index
, cache_dot_index
, cache_grid_index
;
181 int gen_index
= lg
->source
->get_changed_offsets( lg
->last_generation
, cache_graph_index
, cache_dot_index
, cache_grid_index
);
183 if( cache_dirty
|| (gen_index
!= lg
->last_generation
) ) {
185 cairo_t
*cache_cr
= cairo_create( lg
->cache_surface
);
186 cairo_select_font_face(cache_cr
, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
187 cairo_set_font_size(cache_cr
, 9);
189 gdk_cairo_set_source_color(cache_cr
, &sc
);
190 cairo_rectangle(cache_cr
, ox
, oy
, sx
, sy
);
191 cairo_clip_preserve(cache_cr
);
192 cairo_fill(cache_cr
);
194 cairo_impl cache_cimpl
;
195 cache_cimpl
.context
= cache_cr
;
197 lg
->source
->get_changed_offsets( gen_index
, cache_graph_index
, cache_dot_index
, cache_grid_index
);
198 lg
->last_generation
= gen_index
;
200 cairo_set_line_width(cache_cr
, 1);
201 for(int phase
= 1; phase
<= 2; phase
++)
203 for(grid_n
= 0; legend
= std::string(), cairo_set_source_rgba(cache_cr
, 1, 1, 1, 0.5), (grid_n
<cache_grid_index
) && lg
->source
->get_gridline(lg
->source_id
, grid_n
, pos
, vertical
, legend
, &cache_cimpl
); grid_n
++)
205 calf_line_graph_draw_grid( cache_cr
, legend
, vertical
, pos
, phase
, sx
, sy
);
208 grid_n_save
= grid_n
;
210 gdk_cairo_set_source_color(cache_cr
, &sc2
);
211 cairo_set_line_join(cache_cr
, CAIRO_LINE_JOIN_MITER
);
212 cairo_set_line_width(cache_cr
, 1);
213 for(graph_n
= 0; (graph_n
<cache_graph_index
) && lg
->source
->get_graph(lg
->source_id
, graph_n
, data
, 2 * sx
, &cache_cimpl
); graph_n
++)
215 calf_line_graph_draw_graph( cache_cr
, data
, sx
, sy
);
217 gdk_cairo_set_source_color(cache_cr
, &sc3
);
218 for(dot_n
= 0; (dot_n
<cache_dot_index
) && lg
->source
->get_dot(lg
->source_id
, dot_n
, x
, y
, size
= 3, &cache_cimpl
); dot_n
++)
220 int yv
= (int)(oy
+ sy
/ 2 - (sy
/ 2 - 1) * y
);
221 cairo_arc(cache_cr
, ox
+ x
* sx
, yv
, size
, 0, 2 * M_PI
);
222 cairo_fill(cache_cr
);
225 // copy window to cache.
226 cairo_destroy( cache_cr
);
227 calf_line_graph_copy_cache_to_window( lg
, c
);
229 grid_n_save
= cache_grid_index
;
230 graph_n
= cache_graph_index
;
231 dot_n
= cache_dot_index
;
232 calf_line_graph_copy_cache_to_window( lg
, c
);
236 cairo_set_line_width(c
, 1);
237 for(int phase
= 1; phase
<= 2; phase
++)
239 for(int gn
=grid_n_save
; legend
= std::string(), cairo_set_source_rgba(c
, 1, 1, 1, 0.5), lg
->source
->get_gridline(lg
->source_id
, gn
, pos
, vertical
, legend
, &cimpl
); gn
++)
241 calf_line_graph_draw_grid( c
, legend
, vertical
, pos
, phase
, sx
, sy
);
245 gdk_cairo_set_source_color(c
, &sc2
);
246 cairo_set_line_join(c
, CAIRO_LINE_JOIN_MITER
);
247 cairo_set_line_width(c
, 1);
248 for(int gn
= graph_n
; lg
->source
->get_graph(lg
->source_id
, gn
, data
, 2 * sx
, &cimpl
); gn
++)
250 calf_line_graph_draw_graph( c
, data
, sx
, sy
);
252 gdk_cairo_set_source_color(c
, &sc3
);
253 for(int gn
= dot_n
; lg
->source
->get_dot(lg
->source_id
, gn
, x
, y
, size
= 3, &cimpl
); gn
++)
255 int yv
= (int)(oy
+ sy
/ 2 - (sy
/ 2 - 1) * y
);
256 cairo_arc(c
, ox
+ x
* sx
, yv
, size
, 0, 2 * M_PI
);
264 gtk_paint_shadow(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_IN
, NULL
, widget
, NULL
, ox
- 1, oy
- 1, sx
+ 2, sy
+ 2);
265 // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
270 void calf_line_graph_set_square(CalfLineGraph
*graph
, bool is_square
)
272 g_assert(CALF_IS_LINE_GRAPH(graph
));
273 graph
->is_square
= is_square
;
276 int calf_line_graph_update_if(CalfLineGraph
*graph
, int last_drawn_generation
)
278 g_assert(CALF_IS_LINE_GRAPH(graph
));
279 int generation
= last_drawn_generation
;
282 int subgraph
, dot
, gridline
;
283 generation
= graph
->source
->get_changed_offsets(generation
, subgraph
, dot
, gridline
);
284 if (subgraph
== INT_MAX
&& dot
== INT_MAX
&& gridline
== INT_MAX
&& generation
== last_drawn_generation
)
286 gtk_widget_queue_draw(GTK_WIDGET(graph
));
292 calf_line_graph_size_request (GtkWidget
*widget
,
293 GtkRequisition
*requisition
)
295 g_assert(CALF_IS_LINE_GRAPH(widget
));
297 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
301 calf_line_graph_size_allocate (GtkWidget
*widget
,
302 GtkAllocation
*allocation
)
304 g_assert(CALF_IS_LINE_GRAPH(widget
));
305 CalfLineGraph
*lg
= CALF_LINE_GRAPH(widget
);
307 GtkWidgetClass
*parent_class
= (GtkWidgetClass
*) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg
) );
309 if( lg
->cache_surface
)
310 cairo_surface_destroy( lg
->cache_surface
);
311 lg
->cache_surface
= NULL
;
313 widget
->allocation
= *allocation
;
314 GtkAllocation
&a
= widget
->allocation
;
317 if (a
.width
> a
.height
)
319 a
.x
+= (a
.width
- a
.height
) / 2;
322 if (a
.width
< a
.height
)
324 a
.y
+= (a
.height
- a
.width
) / 2;
328 parent_class
->size_allocate( widget
, &a
);
332 calf_line_graph_class_init (CalfLineGraphClass
*klass
)
334 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
335 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
336 widget_class
->expose_event
= calf_line_graph_expose
;
337 widget_class
->size_request
= calf_line_graph_size_request
;
338 widget_class
->size_allocate
= calf_line_graph_size_allocate
;
342 calf_line_graph_init (CalfLineGraph
*self
)
344 GtkWidget
*widget
= GTK_WIDGET(self
);
345 widget
->requisition
.width
= 40;
346 widget
->requisition
.height
= 40;
347 self
->cache_surface
= NULL
;
348 self
->last_generation
= 0;
352 calf_line_graph_new()
354 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH
, NULL
));
358 calf_line_graph_get_type (void)
360 static GType type
= 0;
362 static const GTypeInfo type_info
= {
363 sizeof(CalfLineGraphClass
),
364 NULL
, /* base_init */
365 NULL
, /* base_finalize */
366 (GClassInitFunc
)calf_line_graph_class_init
,
367 NULL
, /* class_finalize */
368 NULL
, /* class_data */
369 sizeof(CalfLineGraph
),
371 (GInstanceInitFunc
)calf_line_graph_init
374 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
376 for (int i
= 0; ; i
++) {
377 char *name
= g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init
) >> 16, i
);
378 if (g_type_from_name(name
)) {
382 type
= g_type_register_static( GTK_TYPE_DRAWING_AREA
,
393 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
396 calf_vumeter_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
398 g_assert(CALF_IS_VUMETER(widget
));
401 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
402 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
404 int sx
= widget
->allocation
.width
- 2, sy
= widget
->allocation
.height
- 2;
406 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
408 if( vu
->cache_surface
== NULL
) {
409 // looks like its either first call or the widget has been resized.
410 // create the cache_surface.
411 cairo_surface_t
*window_surface
= cairo_get_target( c
);
412 vu
->cache_surface
= cairo_surface_create_similar( window_surface
,
414 widget
->allocation
.width
,
415 widget
->allocation
.height
);
417 // And render the meterstuff again.
419 cairo_t
*cache_cr
= cairo_create( vu
->cache_surface
);
420 GdkColor sc
= { 0, 0, 0, 0 };
421 gdk_cairo_set_source_color(cache_cr
, &sc
);
422 cairo_rectangle(cache_cr
, ox
, oy
, sx
, sy
);
423 cairo_fill(cache_cr
);
424 cairo_set_line_width(cache_cr
, 1);
426 for (int x
= ox
; x
<= ox
+ sx
; x
+= 3)
428 float ts
= (x
- ox
) * 1.0 / sx
;
429 float r
= 0.f
, g
= 0.f
, b
= 0.f
;
435 r
= ts
/ 0.75, g
= 1, b
= 0;
437 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
438 // if (vu->value < ts || vu->value <= 0)
439 // r *= 0.5, g *= 0.5, b *= 0.5;
441 case VU_MONOCHROME_REVERSE
:
443 // if (!(vu->value < ts) || vu->value >= 1.0)
444 // r *= 0.5, g *= 0.5, b *= 0.5;
448 // if (vu->value < ts || vu->value <= 0)
449 // r *= 0.5, g *= 0.5, b *= 0.5;
452 GdkColor sc2
= { 0, (guint16
)(65535 * r
), (guint16
)(65535 * g
), (guint16
)(65535 * b
) };
453 gdk_cairo_set_source_color(cache_cr
, &sc2
);
454 cairo_move_to(cache_cr
, x
, oy
);
455 cairo_line_to(cache_cr
, x
, oy
+ sy
+ 1);
456 cairo_move_to(cache_cr
, x
, oy
+ sy
);
457 cairo_line_to(cache_cr
, x
, oy
);
458 cairo_stroke(cache_cr
);
460 cairo_destroy( cache_cr
);
463 cairo_set_source_surface( c
, vu
->cache_surface
, 0,0 );
465 cairo_set_source_rgba( c
, 0,0,0, 0.5 );
467 if( vu
->mode
== VU_MONOCHROME_REVERSE
)
468 cairo_rectangle( c
, ox
,oy
, vu
->value
* (sx
-ox
) + 1, sy
-oy
+1 );
470 cairo_rectangle( c
, ox
+ vu
->value
* (sx
-ox
), oy
, sx
-ox
+1, sy
-oy
+1 );
478 gtk_paint_shadow(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_IN
, NULL
, widget
, NULL
, ox
- 1, oy
- 1, sx
+ 2, sy
+ 2);
479 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
485 calf_vumeter_size_request (GtkWidget
*widget
,
486 GtkRequisition
*requisition
)
488 g_assert(CALF_IS_VUMETER(widget
));
490 requisition
->width
= 50;
491 requisition
->height
= 14;
495 calf_vumeter_size_allocate (GtkWidget
*widget
,
496 GtkAllocation
*allocation
)
498 g_assert(CALF_IS_VUMETER(widget
));
499 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
501 GtkWidgetClass
*parent_class
= (GtkWidgetClass
*) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu
) );
503 parent_class
->size_allocate( widget
, allocation
);
505 if( vu
->cache_surface
)
506 cairo_surface_destroy( vu
->cache_surface
);
507 vu
->cache_surface
= NULL
;
511 calf_vumeter_class_init (CalfVUMeterClass
*klass
)
513 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
514 widget_class
->expose_event
= calf_vumeter_expose
;
515 widget_class
->size_request
= calf_vumeter_size_request
;
516 widget_class
->size_allocate
= calf_vumeter_size_allocate
;
520 calf_vumeter_init (CalfVUMeter
*self
)
522 GtkWidget
*widget
= GTK_WIDGET(self
);
523 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
524 widget
->requisition
.width
= 50;
525 widget
->requisition
.height
= 15;
527 self
->cache_surface
= NULL
;
533 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER
, NULL
));
537 calf_vumeter_get_type (void)
539 static GType type
= 0;
541 static const GTypeInfo type_info
= {
542 sizeof(CalfVUMeterClass
),
543 NULL
, /* base_init */
544 NULL
, /* base_finalize */
545 (GClassInitFunc
)calf_vumeter_class_init
,
546 NULL
, /* class_finalize */
547 NULL
, /* class_data */
550 (GInstanceInitFunc
)calf_vumeter_init
553 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
555 for (int i
= 0; ; i
++) {
556 char *name
= g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init
) >> 16, i
);
557 if (g_type_from_name(name
)) {
561 type
= g_type_register_static( GTK_TYPE_DRAWING_AREA
,
572 extern void calf_vumeter_set_value(CalfVUMeter
*meter
, float value
)
574 if (value
!= meter
->value
)
576 meter
->value
= value
;
577 gtk_widget_queue_draw(GTK_WIDGET(meter
));
581 extern float calf_vumeter_get_value(CalfVUMeter
*meter
)
586 extern void calf_vumeter_set_mode(CalfVUMeter
*meter
, CalfVUMeterMode mode
)
588 if (mode
!= meter
->mode
)
591 gtk_widget_queue_draw(GTK_WIDGET(meter
));
595 extern CalfVUMeterMode
calf_vumeter_get_mode(CalfVUMeter
*meter
)
600 ///////////////////////////////////////// knob ///////////////////////////////////////////////
603 calf_knob_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
605 g_assert(CALF_IS_KNOB(widget
));
607 CalfKnob
*self
= CALF_KNOB(widget
);
608 GdkWindow
*window
= widget
->window
;
609 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
611 // printf("adjustment = %p value = %f\n", adj, adj->value);
612 int ox
= widget
->allocation
.x
, oy
= widget
->allocation
.y
;
614 ox
+= (widget
->allocation
.width
- 40) / 2;
615 oy
+= (widget
->allocation
.height
- 40) / 2;
617 int phase
= (int)((adj
->value
- adj
->lower
) * 64 / (adj
->upper
- adj
->lower
));
618 // skip middle phase except for true middle value
619 if (self
->knob_type
== 1 && phase
== 32) {
620 double pt
= (adj
->value
- adj
->lower
) * 2.0 / (adj
->upper
- adj
->lower
) - 1.0;
626 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
627 if (self
->knob_type
== 3 && !(phase
% 16)) {
630 double nom
= adj
->lower
+ phase
* (adj
->upper
- adj
->lower
) / 64.0;
631 double diff
= (adj
->value
- nom
) / (adj
->upper
- adj
->lower
);
633 phase
= (phase
+ 1) % 64;
635 phase
= (phase
+ 63) % 64;
637 gdk_draw_pixbuf(GDK_DRAWABLE(widget
->window
), widget
->style
->fg_gc
[0], CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget
))->knob_image
, phase
* 40, self
->knob_type
* 40, ox
, oy
, 40, 40, GDK_RGB_DITHER_NORMAL
, 0, 0);
638 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
639 if (gtk_widget_is_focus(widget
))
641 gtk_paint_focus(widget
->style
, window
, GTK_STATE_NORMAL
, NULL
, widget
, NULL
, ox
, oy
, 40, 40);
648 calf_knob_size_request (GtkWidget
*widget
,
649 GtkRequisition
*requisition
)
651 g_assert(CALF_IS_KNOB(widget
));
653 // width/height is hardwired at 40px now
654 requisition
->width
= 40;
655 requisition
->height
= 40;
659 calf_knob_incr (GtkWidget
*widget
, int dir_down
)
661 g_assert(CALF_IS_KNOB(widget
));
662 CalfKnob
*self
= CALF_KNOB(widget
);
663 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
665 int oldstep
= (int)(0.5f
+ (adj
->value
- adj
->lower
) / adj
->step_increment
);
667 int nsteps
= (int)(0.5f
+ (adj
->upper
- adj
->lower
) / adj
->step_increment
); // less 1 actually
672 if (self
->knob_type
== 3 && step
>= nsteps
)
674 if (self
->knob_type
== 3 && step
< 0)
675 step
= nsteps
- (nsteps
- step
) % nsteps
;
677 // trying to reduce error cumulation here, by counting from lowest or from highest
678 float value
= adj
->lower
+ step
* double(adj
->upper
- adj
->lower
) / nsteps
;
679 gtk_range_set_value(GTK_RANGE(widget
), value
);
680 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
684 calf_knob_key_press (GtkWidget
*widget
, GdkEventKey
*event
)
686 g_assert(CALF_IS_KNOB(widget
));
687 CalfKnob
*self
= CALF_KNOB(widget
);
688 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
690 switch(event
->keyval
)
693 gtk_range_set_value(GTK_RANGE(widget
), adj
->lower
);
697 gtk_range_set_value(GTK_RANGE(widget
), adj
->upper
);
701 calf_knob_incr(widget
, 0);
705 calf_knob_incr(widget
, 1);
710 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
711 self
->start_y
= self
->last_y
;
719 calf_knob_key_release (GtkWidget
*widget
, GdkEventKey
*event
)
721 g_assert(CALF_IS_KNOB(widget
));
722 CalfKnob
*self
= CALF_KNOB(widget
);
724 if(event
->keyval
== GDK_Shift_L
|| event
->keyval
== GDK_Shift_R
)
726 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
727 self
->start_y
= self
->last_y
;
735 calf_knob_button_press (GtkWidget
*widget
, GdkEventButton
*event
)
737 g_assert(CALF_IS_KNOB(widget
));
738 CalfKnob
*self
= CALF_KNOB(widget
);
740 // CalfKnob *lg = CALF_KNOB(widget);
741 gtk_widget_grab_focus(widget
);
742 gtk_grab_add(widget
);
743 self
->start_x
= event
->x
;
744 self
->start_y
= event
->y
;
745 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
751 calf_knob_button_release (GtkWidget
*widget
, GdkEventButton
*event
)
753 g_assert(CALF_IS_KNOB(widget
));
755 if (GTK_WIDGET_HAS_GRAB(widget
))
756 gtk_grab_remove(widget
);
760 static inline float endless(float value
)
763 return fmod(value
, 1.f
);
765 return fmod(1.f
- fmod(1.f
- value
, 1.f
), 1.f
);
768 static inline float deadzone(float value
, float incr
, float scale
)
770 float dzw
= 10 / scale
;
778 if (value
>= (0.5 - dzw
) && value
<= (0.5 + dzw
))
786 calf_knob_pointer_motion (GtkWidget
*widget
, GdkEventMotion
*event
)
788 g_assert(CALF_IS_KNOB(widget
));
789 CalfKnob
*self
= CALF_KNOB(widget
);
791 float scale
= (event
->state
& GDK_SHIFT_MASK
) ? 1000 : 100;
792 gboolean moved
= FALSE
;
794 if (GTK_WIDGET_HAS_GRAB(widget
))
796 if (self
->knob_type
== 3)
798 gtk_range_set_value(GTK_RANGE(widget
), endless(self
->start_value
- (event
->y
- self
->start_y
) / scale
));
801 if (self
->knob_type
== 1)
803 gtk_range_set_value(GTK_RANGE(widget
), deadzone(self
->start_value
, -(event
->y
- self
->start_y
) / scale
, scale
));
807 gtk_range_set_value(GTK_RANGE(widget
), self
->start_value
- (event
->y
- self
->start_y
) / scale
);
811 self
->last_y
= event
->y
;
816 calf_knob_scroll (GtkWidget
*widget
, GdkEventScroll
*event
)
818 calf_knob_incr(widget
, event
->direction
);
823 calf_knob_class_init (CalfKnobClass
*klass
)
825 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
826 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
827 widget_class
->expose_event
= calf_knob_expose
;
828 widget_class
->size_request
= calf_knob_size_request
;
829 widget_class
->button_press_event
= calf_knob_button_press
;
830 widget_class
->button_release_event
= calf_knob_button_release
;
831 widget_class
->motion_notify_event
= calf_knob_pointer_motion
;
832 widget_class
->key_press_event
= calf_knob_key_press
;
833 widget_class
->key_release_event
= calf_knob_key_release
;
834 widget_class
->scroll_event
= calf_knob_scroll
;
835 GError
*error
= NULL
;
836 klass
->knob_image
= gdk_pixbuf_new_from_file(PKGLIBDIR
"/knob.png", &error
);
837 g_assert(klass
->knob_image
!= NULL
);
841 calf_knob_init (CalfKnob
*self
)
843 GtkWidget
*widget
= GTK_WIDGET(self
);
844 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self
), GTK_CAN_FOCUS
);
845 widget
->requisition
.width
= 40;
846 widget
->requisition
.height
= 40;
852 GtkAdjustment
*adj
= (GtkAdjustment
*)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
853 return calf_knob_new_with_adjustment(adj
);
856 static gboolean
calf_knob_value_changed(gpointer obj
)
858 GtkWidget
*widget
= (GtkWidget
*)obj
;
859 gtk_widget_queue_draw(widget
);
863 GtkWidget
*calf_knob_new_with_adjustment(GtkAdjustment
*_adjustment
)
865 GtkWidget
*widget
= GTK_WIDGET( g_object_new (CALF_TYPE_KNOB
, NULL
));
867 gtk_range_set_adjustment(GTK_RANGE(widget
), _adjustment
);
868 gtk_signal_connect(GTK_OBJECT(widget
), "value-changed", G_CALLBACK(calf_knob_value_changed
), widget
);
874 calf_knob_get_type (void)
876 static GType type
= 0;
879 static const GTypeInfo type_info
= {
880 sizeof(CalfKnobClass
),
881 NULL
, /* base_init */
882 NULL
, /* base_finalize */
883 (GClassInitFunc
)calf_knob_class_init
,
884 NULL
, /* class_finalize */
885 NULL
, /* class_data */
888 (GInstanceInitFunc
)calf_knob_init
891 for (int i
= 0; ; i
++) {
892 char *name
= g_strdup_printf("CalfKnob%u%d",
893 ((unsigned int)(intptr_t)calf_knob_class_init
) >> 16, i
);
894 if (g_type_from_name(name
)) {
898 type
= g_type_register_static(GTK_TYPE_RANGE
,