debug printfs...
[calf.git] / src / custom_ctl.cpp
blobf29167be2a8936d10d0b8cc8b9d452d3c46a3ceb
1 /* Calf DSP Library
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.
20 #include "config.h"
21 #include <calf/custom_ctl.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <cairo/cairo.h>
24 #include <math.h>
25 #include <gdk/gdk.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
31 GTypeModule *module;
33 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;
48 static void
49 calf_line_graph_copy_cache_to_window( CalfLineGraph *lg, cairo_t *c )
51 cairo_save( c );
52 cairo_set_source_surface( c, lg->cache_surface, 0,0 );
53 cairo_paint( c );
54 cairo_restore( c );
57 static void
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 );
67 static void
68 calf_line_graph_draw_grid( cairo_t *c, std::string &legend, bool vertical, float pos, int phase, int sx, int sy )
70 int ox=1, oy=1;
71 cairo_text_extents_t tx;
72 if (!legend.empty())
73 cairo_text_extents(c, legend.c_str(), &tx);
74 if (vertical)
76 float x = floor(ox + pos * sx) + 0.5;
77 if (phase == 1)
79 cairo_move_to(c, x, oy);
80 cairo_line_to(c, x, oy + sy);
81 cairo_stroke(c);
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());
90 else
92 float y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
93 if (phase == 1)
95 cairo_move_to(c, ox, y);
96 cairo_line_to(c, ox + sx, y);
97 cairo_stroke(c);
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());
107 static void
108 calf_line_graph_draw_graph( cairo_t *c, float *data, int sx, int sy )
110 int ox=1, oy=1;
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;
117 if (i)
118 cairo_line_to(c, ox + i * 0.5, y);
119 else
120 cairo_move_to(c, ox, y);
122 cairo_stroke(c);
125 static gboolean
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;
132 int ox = 1, oy = 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,
148 CAIRO_CONTENT_COLOR,
149 widget->allocation.width,
150 widget->allocation.height );
151 //cairo_set_source_surface( cache_cr, window_surface, 0,0 );
152 //cairo_paint( cache_cr );
154 cache_dirty = 1;
157 gdk_cairo_set_source_color(c, &sc);
158 cairo_rectangle(c, ox, oy, sx, sy);
159 cairo_clip_preserve(c);
160 cairo_fill(c);
161 cairo_impl cimpl;
162 cimpl.context = c;
163 cairo_select_font_face(c, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
164 cairo_set_font_size(c, 9);
166 if (lg->source) {
168 float pos = 0;
169 bool vertical = false;
170 std::string legend;
171 float *data = new float[2 * sx];
172 GdkColor sc2 = { 0, 0, 65535, 0 };
173 float x, y;
174 int size = 0;
175 GdkColor sc3 = { 0, 32767, 65535, 0 };
177 int graph_n, grid_n, dot_n, grid_n_save;
179 int cache_graph_index, cache_dot_index, cache_grid_index;
180 int gen_index = lg->source->get_changed_offsets( lg->last_generation, cache_graph_index, cache_dot_index, cache_grid_index );
182 if( cache_dirty || (gen_index != lg->last_generation) ) {
183 lg->source->get_changed_offsets( gen_index, cache_graph_index, cache_dot_index, cache_grid_index );
184 lg->last_generation = gen_index;
186 printf( "oki... cache_graph_index = %d\n", cache_graph_index );
188 cairo_set_line_width(c, 1);
189 for(int phase = 1; phase <= 2; phase++)
191 for(grid_n = 0; legend = std::string(), cairo_set_source_rgba(c, 1, 1, 1, 0.5), (grid_n<cache_grid_index) && lg->source->get_gridline(lg->source_id, grid_n, pos, vertical, legend, &cimpl); grid_n++)
193 printf( "draw gridline %d\n", grid_n );
194 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
197 grid_n_save = grid_n-1;
199 gdk_cairo_set_source_color(c, &sc2);
200 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
201 cairo_set_line_width(c, 1);
202 for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, 2 * sx, &cimpl); graph_n++)
204 calf_line_graph_draw_graph( c, data, sx, sy );
206 gdk_cairo_set_source_color(c, &sc3);
207 for(dot_n = 0; (dot_n<cache_dot_index) && lg->source->get_dot(lg->source_id, dot_n, x, y, size = 3, &cimpl); dot_n++)
209 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
210 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
211 cairo_fill(c);
214 // copy window to cache.
215 calf_line_graph_copy_window_to_cache( lg, c );
216 } else {
217 grid_n_save = cache_grid_index;
218 graph_n = cache_graph_index;
219 dot_n = cache_dot_index;
220 calf_line_graph_copy_cache_to_window( lg, c );
222 printf( "cache paint ... cache_graph_index = %d\n", cache_graph_index );
226 cairo_set_line_width(c, 1);
227 for(int phase = 1; phase <= 2; phase++)
229 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++)
231 printf( "after cache:draw gridline %d\n", gn );
232 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
236 gdk_cairo_set_source_color(c, &sc2);
237 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
238 cairo_set_line_width(c, 1);
239 for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
241 //printf( "uncached graph draw = %d\n", gn );
242 calf_line_graph_draw_graph( c, data, sx, sy );
244 gdk_cairo_set_source_color(c, &sc3);
245 for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
247 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
248 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
249 cairo_fill(c);
251 delete []data;
254 cairo_destroy(c);
256 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
257 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
259 return TRUE;
262 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
264 g_assert(CALF_IS_LINE_GRAPH(graph));
265 graph->is_square = is_square;
268 static void
269 calf_line_graph_size_request (GtkWidget *widget,
270 GtkRequisition *requisition)
272 g_assert(CALF_IS_LINE_GRAPH(widget));
274 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
275 requisition->width = 40;
276 requisition->height = 40;
279 static void
280 calf_line_graph_size_allocate (GtkWidget *widget,
281 GtkAllocation *allocation)
283 g_assert(CALF_IS_LINE_GRAPH(widget));
284 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
286 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
288 if( lg->cache_surface )
289 cairo_surface_destroy( lg->cache_surface );
290 lg->cache_surface = NULL;
292 widget->allocation = *allocation;
293 GtkAllocation &a = widget->allocation;
294 if (lg->is_square)
296 if (a.width > a.height)
298 a.x += (a.width - a.height) / 2;
299 a.width = a.height;
301 if (a.width < a.height)
303 a.y += (a.height - a.width) / 2;
304 a.height = a.width;
307 parent_class->size_allocate( widget, allocation );
310 static void
311 calf_line_graph_class_init (CalfLineGraphClass *klass)
313 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
314 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
315 widget_class->expose_event = calf_line_graph_expose;
316 widget_class->size_request = calf_line_graph_size_request;
317 widget_class->size_allocate = calf_line_graph_size_allocate;
320 static void
321 calf_line_graph_init (CalfLineGraph *self)
323 GtkWidget *widget = GTK_WIDGET(self);
324 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
325 //widget->requisition.width = 40;
326 //widget->requisition.height = 40;
327 gtk_widget_set_size_request( widget, 40, 40 );
328 self->cache_surface = NULL;
331 GtkWidget *
332 calf_line_graph_new()
334 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
337 GType
338 calf_line_graph_get_type (void)
340 static GType type = 0;
341 if (!type) {
342 static const GTypeInfo type_info = {
343 sizeof(CalfLineGraphClass),
344 NULL, /* base_init */
345 NULL, /* base_finalize */
346 (GClassInitFunc)calf_line_graph_class_init,
347 NULL, /* class_finalize */
348 NULL, /* class_data */
349 sizeof(CalfLineGraph),
350 0, /* n_preallocs */
351 (GInstanceInitFunc)calf_line_graph_init
354 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
356 for (int i = 0; ; i++) {
357 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
358 if (g_type_from_name(name)) {
359 free(name);
360 continue;
362 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
363 name,
364 type_info_copy,
365 (GTypeFlags)0);
366 free(name);
367 break;
370 return type;
373 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
375 static gboolean
376 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
378 g_assert(CALF_IS_VUMETER(widget));
381 CalfVUMeter *vu = CALF_VUMETER(widget);
382 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
383 int ox = 1, oy = 1;
384 int sx = widget->allocation.width - 2, sy = widget->allocation.height - 2;
386 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
388 GdkColor sc = { 0, 0, 0, 0 };
389 gdk_cairo_set_source_color(c, &sc);
390 cairo_rectangle(c, ox, oy, sx, sy);
391 cairo_fill(c);
392 cairo_set_line_width(c, 1);
394 CalfVUMeterMode mode = vu->mode;
396 for (int x = ox; x <= ox + sx; x += 3)
398 float ts = (x - ox) * 1.0 / sx;
399 float r = 0.f, g = 0.f, b = 0.f;
400 switch(mode)
402 case VU_STANDARD:
403 default:
404 if (ts < 0.75)
405 r = ts / 0.75, g = 1, b = 0;
406 else
407 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
408 if (vu->value < ts || vu->value <= 0)
409 r *= 0.5, g *= 0.5, b *= 0.5;
410 break;
411 case VU_MONOCHROME_REVERSE:
412 r = 1, g = 1, b = 0;
413 if (!(vu->value < ts) || vu->value >= 1.0)
414 r *= 0.5, g *= 0.5, b *= 0.5;
415 break;
416 case VU_MONOCHROME:
417 r = 1, g = 1, b = 0;
418 if (vu->value < ts || vu->value <= 0)
419 r *= 0.5, g *= 0.5, b *= 0.5;
420 break;
422 GdkColor sc2 = { 0, (guint16)(65535 * r), (guint16)(65535 * g), (guint16)(65535 * b) };
423 gdk_cairo_set_source_color(c, &sc2);
424 cairo_move_to(c, x, oy);
425 cairo_line_to(c, x, oy + sy + 1);
426 cairo_move_to(c, x, oy + sy);
427 cairo_line_to(c, x, oy );
428 cairo_stroke(c);
431 cairo_destroy(c);
433 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
434 printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
436 return TRUE;
439 static void
440 calf_vumeter_size_request (GtkWidget *widget,
441 GtkRequisition *requisition)
443 g_assert(CALF_IS_VUMETER(widget));
445 requisition->width = 50;
446 requisition->height = 14;
449 static void
450 calf_vumeter_size_allocate (GtkWidget *widget,
451 GtkAllocation *allocation)
453 g_assert(CALF_IS_VUMETER(widget));
455 widget->allocation = *allocation;
458 static void
459 calf_vumeter_class_init (CalfVUMeterClass *klass)
461 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
462 widget_class->expose_event = calf_vumeter_expose;
463 widget_class->size_request = calf_vumeter_size_request;
464 //widget_class->size_allocate = calf_vumeter_size_allocate;
467 static void
468 calf_vumeter_init (CalfVUMeter *self)
470 GtkWidget *widget = GTK_WIDGET(self);
471 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
472 widget->requisition.width = 50;
473 widget->requisition.height = 15;
474 self->value = 0.5;
477 GtkWidget *
478 calf_vumeter_new()
480 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
483 GType
484 calf_vumeter_get_type (void)
486 static GType type = 0;
487 if (!type) {
488 static const GTypeInfo type_info = {
489 sizeof(CalfVUMeterClass),
490 NULL, /* base_init */
491 NULL, /* base_finalize */
492 (GClassInitFunc)calf_vumeter_class_init,
493 NULL, /* class_finalize */
494 NULL, /* class_data */
495 sizeof(CalfVUMeter),
496 0, /* n_preallocs */
497 (GInstanceInitFunc)calf_vumeter_init
500 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
502 for (int i = 0; ; i++) {
503 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
504 if (g_type_from_name(name)) {
505 free(name);
506 continue;
508 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
509 name,
510 type_info_copy,
511 (GTypeFlags)0);
512 free(name);
513 break;
516 return type;
519 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
521 if (value != meter->value)
523 meter->value = value;
524 gtk_widget_queue_draw(GTK_WIDGET(meter));
528 extern float calf_vumeter_get_value(CalfVUMeter *meter)
530 return meter->value;
533 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
535 if (mode != meter->mode)
537 meter->mode = mode;
538 gtk_widget_queue_draw(GTK_WIDGET(meter));
542 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
544 return meter->mode;
547 ///////////////////////////////////////// knob ///////////////////////////////////////////////
549 static gboolean
550 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
552 g_assert(CALF_IS_KNOB(widget));
554 CalfKnob *self = CALF_KNOB(widget);
555 GdkWindow *window = widget->window;
556 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
558 // printf("adjustment = %p value = %f\n", adj, adj->value);
559 int ox = widget->allocation.x, oy = widget->allocation.y;
561 ox += (widget->allocation.width - 40) / 2;
562 oy += (widget->allocation.height - 40) / 2;
564 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
565 // skip middle phase except for true middle value
566 if (self->knob_type == 1 && phase == 32) {
567 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
568 if (pt < 0)
569 phase = 31;
570 if (pt > 0)
571 phase = 33;
573 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
574 if (self->knob_type == 3 && !(phase % 16)) {
575 if (phase == 64)
576 phase = 0;
577 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
578 double diff = (adj->value - nom) / (adj->upper - adj->lower);
579 if (diff > 0.0001)
580 phase = (phase + 1) % 64;
581 if (diff < -0.0001)
582 phase = (phase + 63) % 64;
584 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);
585 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
586 if (gtk_widget_is_focus(widget))
588 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, 40, 40);
591 return TRUE;
594 static void
595 calf_knob_size_request (GtkWidget *widget,
596 GtkRequisition *requisition)
598 g_assert(CALF_IS_KNOB(widget));
600 // width/height is hardwired at 40px now
601 requisition->width = 40;
602 requisition->height = 40;
605 static void
606 calf_knob_incr (GtkWidget *widget, int dir_down)
608 g_assert(CALF_IS_KNOB(widget));
609 CalfKnob *self = CALF_KNOB(widget);
610 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
612 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
613 int step;
614 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
615 if (dir_down)
616 step = oldstep - 1;
617 else
618 step = oldstep + 1;
619 if (self->knob_type == 3 && step >= nsteps)
620 step %= nsteps;
621 if (self->knob_type == 3 && step < 0)
622 step = nsteps - (nsteps - step) % nsteps;
624 // trying to reduce error cumulation here, by counting from lowest or from highest
625 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
626 gtk_range_set_value(GTK_RANGE(widget), value);
627 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
630 static gboolean
631 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
633 g_assert(CALF_IS_KNOB(widget));
634 CalfKnob *self = CALF_KNOB(widget);
635 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
637 switch(event->keyval)
639 case GDK_Home:
640 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
641 return TRUE;
643 case GDK_End:
644 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
645 return TRUE;
647 case GDK_Up:
648 calf_knob_incr(widget, 0);
649 return TRUE;
651 case GDK_Down:
652 calf_knob_incr(widget, 1);
653 return TRUE;
655 case GDK_Shift_L:
656 case GDK_Shift_R:
657 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
658 self->start_y = self->last_y;
659 return TRUE;
662 return FALSE;
665 static gboolean
666 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
668 g_assert(CALF_IS_KNOB(widget));
669 CalfKnob *self = CALF_KNOB(widget);
671 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
673 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
674 self->start_y = self->last_y;
675 return TRUE;
678 return FALSE;
681 static gboolean
682 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
684 g_assert(CALF_IS_KNOB(widget));
685 CalfKnob *self = CALF_KNOB(widget);
687 // CalfKnob *lg = CALF_KNOB(widget);
688 gtk_widget_grab_focus(widget);
689 gtk_grab_add(widget);
690 self->start_x = event->x;
691 self->start_y = event->y;
692 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
694 return TRUE;
697 static gboolean
698 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
700 g_assert(CALF_IS_KNOB(widget));
702 if (GTK_WIDGET_HAS_GRAB(widget))
703 gtk_grab_remove(widget);
704 return FALSE;
707 static inline float endless(float value)
709 if (value >= 0)
710 return fmod(value, 1.f);
711 else
712 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
715 static inline float deadzone(float value, float incr, float scale)
717 float dzw = 10 / scale;
718 if (value >= 0.501)
719 value += dzw;
720 if (value < 0.499)
721 value -= dzw;
723 value += incr;
725 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
726 return 0.5;
727 if (value < 0.5)
728 return value + dzw;
729 return value - dzw;
732 static gboolean
733 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
735 g_assert(CALF_IS_KNOB(widget));
736 CalfKnob *self = CALF_KNOB(widget);
738 float scale = (event->state & GDK_SHIFT_MASK) ? 1000 : 100;
739 gboolean moved = FALSE;
741 if (GTK_WIDGET_HAS_GRAB(widget))
743 if (self->knob_type == 3)
745 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
747 else
748 if (self->knob_type == 1)
750 gtk_range_set_value(GTK_RANGE(widget), deadzone(self->start_value, -(event->y - self->start_y) / scale, scale));
752 else
754 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
756 moved = TRUE;
758 self->last_y = event->y;
759 return moved;
762 static gboolean
763 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
765 calf_knob_incr(widget, event->direction);
766 return TRUE;
769 static void
770 calf_knob_class_init (CalfKnobClass *klass)
772 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
773 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
774 widget_class->expose_event = calf_knob_expose;
775 widget_class->size_request = calf_knob_size_request;
776 widget_class->button_press_event = calf_knob_button_press;
777 widget_class->button_release_event = calf_knob_button_release;
778 widget_class->motion_notify_event = calf_knob_pointer_motion;
779 widget_class->key_press_event = calf_knob_key_press;
780 widget_class->key_release_event = calf_knob_key_release;
781 widget_class->scroll_event = calf_knob_scroll;
782 GError *error = NULL;
783 klass->knob_image = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob.png", &error);
784 g_assert(klass->knob_image != NULL);
787 static void
788 calf_knob_init (CalfKnob *self)
790 GtkWidget *widget = GTK_WIDGET(self);
791 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
792 widget->requisition.width = 40;
793 widget->requisition.height = 40;
796 GtkWidget *
797 calf_knob_new()
799 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
800 return calf_knob_new_with_adjustment(adj);
803 static gboolean calf_knob_value_changed(gpointer obj)
805 GtkWidget *widget = (GtkWidget *)obj;
806 gtk_widget_queue_draw(widget);
807 return FALSE;
810 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
812 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
813 if (widget) {
814 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
815 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
817 return widget;
820 GType
821 calf_knob_get_type (void)
823 static GType type = 0;
824 if (!type) {
826 static const GTypeInfo type_info = {
827 sizeof(CalfKnobClass),
828 NULL, /* base_init */
829 NULL, /* base_finalize */
830 (GClassInitFunc)calf_knob_class_init,
831 NULL, /* class_finalize */
832 NULL, /* class_data */
833 sizeof(CalfKnob),
834 0, /* n_preallocs */
835 (GInstanceInitFunc)calf_knob_init
838 for (int i = 0; ; i++) {
839 char *name = g_strdup_printf("CalfKnob%u%d",
840 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
841 if (g_type_from_name(name)) {
842 free(name);
843 continue;
845 type = g_type_register_static(GTK_TYPE_RANGE,
846 name,
847 &type_info,
848 (GTypeFlags)0);
849 free(name);
850 break;
853 return type;