+ Organ: add pitch bend inertia
[calf.git] / src / custom_ctl.cpp
blobc3b17a0dd7808d00a9c56c697ab64fa126371274
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 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);
162 cairo_clip(c);
163 cairo_impl cimpl;
164 cimpl.context = c;
166 if (lg->source) {
169 float pos = 0;
170 bool vertical = false;
171 std::string legend;
172 float *data = new float[2 * sx];
173 GdkColor sc2 = { 0, 0, 65535, 0 };
174 float x, y;
175 int size = 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 );
228 } else {
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);
257 cairo_fill(c);
259 delete []data;
262 cairo_destroy(c);
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);
267 return TRUE;
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;
280 if (graph->source)
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)
285 return generation;
286 gtk_widget_queue_draw(GTK_WIDGET(graph));
288 return generation;
291 static void
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);
300 static void
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;
315 if (lg->is_square)
317 if (a.width > a.height)
319 a.x += (a.width - a.height) / 2;
320 a.width = a.height;
322 if (a.width < a.height)
324 a.y += (a.height - a.width) / 2;
325 a.height = a.width;
328 parent_class->size_allocate( widget, &a );
331 static void
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;
341 static void
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;
351 GtkWidget *
352 calf_line_graph_new()
354 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
357 GType
358 calf_line_graph_get_type (void)
360 static GType type = 0;
361 if (!type) {
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),
370 0, /* n_preallocs */
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)) {
379 free(name);
380 continue;
382 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
383 name,
384 type_info_copy,
385 (GTypeFlags)0);
386 free(name);
387 break;
390 return type;
393 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
395 static gboolean
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;
403 int ox = 1, oy = 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,
413 CAIRO_CONTENT_COLOR,
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;
430 switch(vu->mode)
432 case VU_STANDARD:
433 default:
434 if (ts < 0.75)
435 r = ts / 0.75, g = 1, b = 0;
436 else
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;
440 break;
441 case VU_MONOCHROME_REVERSE:
442 r = 1, g = 1, b = 0;
443 // if (!(vu->value < ts) || vu->value >= 1.0)
444 // r *= 0.5, g *= 0.5, b *= 0.5;
445 break;
446 case VU_MONOCHROME:
447 r = 1, g = 1, b = 0;
448 // if (vu->value < ts || vu->value <= 0)
449 // r *= 0.5, g *= 0.5, b *= 0.5;
450 break;
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 );
464 cairo_paint( c );
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 );
469 else
470 cairo_rectangle( c, ox + vu->value * (sx-ox), oy, sx-ox+1, sy-oy+1 );
472 cairo_fill( c );
476 cairo_destroy(c);
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);
481 return TRUE;
484 static void
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;
494 static void
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;
510 static void
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;
519 static void
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;
526 self->value = 0.5;
527 self->cache_surface = NULL;
530 GtkWidget *
531 calf_vumeter_new()
533 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
536 GType
537 calf_vumeter_get_type (void)
539 static GType type = 0;
540 if (!type) {
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 */
548 sizeof(CalfVUMeter),
549 0, /* n_preallocs */
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)) {
558 free(name);
559 continue;
561 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
562 name,
563 type_info_copy,
564 (GTypeFlags)0);
565 free(name);
566 break;
569 return type;
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)
583 return meter->value;
586 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
588 if (mode != meter->mode)
590 meter->mode = mode;
591 gtk_widget_queue_draw(GTK_WIDGET(meter));
595 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
597 return meter->mode;
600 ///////////////////////////////////////// knob ///////////////////////////////////////////////
602 static gboolean
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;
621 if (pt < 0)
622 phase = 31;
623 if (pt > 0)
624 phase = 33;
626 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
627 if (self->knob_type == 3 && !(phase % 16)) {
628 if (phase == 64)
629 phase = 0;
630 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
631 double diff = (adj->value - nom) / (adj->upper - adj->lower);
632 if (diff > 0.0001)
633 phase = (phase + 1) % 64;
634 if (diff < -0.0001)
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);
644 return TRUE;
647 static void
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;
658 static void
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);
666 int step;
667 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
668 if (dir_down)
669 step = oldstep - 1;
670 else
671 step = oldstep + 1;
672 if (self->knob_type == 3 && step >= nsteps)
673 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);
683 static gboolean
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)
692 case GDK_Home:
693 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
694 return TRUE;
696 case GDK_End:
697 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
698 return TRUE;
700 case GDK_Up:
701 calf_knob_incr(widget, 0);
702 return TRUE;
704 case GDK_Down:
705 calf_knob_incr(widget, 1);
706 return TRUE;
708 case GDK_Shift_L:
709 case GDK_Shift_R:
710 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
711 self->start_y = self->last_y;
712 return TRUE;
715 return FALSE;
718 static gboolean
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;
728 return TRUE;
731 return FALSE;
734 static gboolean
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));
747 return TRUE;
750 static gboolean
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);
757 return FALSE;
760 static inline float endless(float value)
762 if (value >= 0)
763 return fmod(value, 1.f);
764 else
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;
771 if (value >= 0.501)
772 value += dzw;
773 if (value < 0.499)
774 value -= dzw;
776 value += incr;
778 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
779 return 0.5;
780 if (value < 0.5)
781 return value + dzw;
782 return value - dzw;
785 static gboolean
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));
800 else
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));
805 else
807 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
809 moved = TRUE;
811 self->last_y = event->y;
812 return moved;
815 static gboolean
816 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
818 calf_knob_incr(widget, event->direction);
819 return TRUE;
822 static void
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);
840 static void
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;
849 GtkWidget *
850 calf_knob_new()
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);
860 return FALSE;
863 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
865 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
866 if (widget) {
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);
870 return widget;
873 GType
874 calf_knob_get_type (void)
876 static GType type = 0;
877 if (!type) {
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 */
886 sizeof(CalfKnob),
887 0, /* n_preallocs */
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)) {
895 free(name);
896 continue;
898 type = g_type_register_static(GTK_TYPE_RANGE,
899 name,
900 &type_info,
901 (GTypeFlags)0);
902 free(name);
903 break;
906 return type;