Merge commit 'torbenh/master'
[calf.git] / src / custom_ctl.cpp
blob05f1d5c53fb7d2fb77551fede65d5645a7199407
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 cairo_set_line_width(c, 1);
187 for(int phase = 1; phase <= 2; phase++)
189 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++)
191 printf( "draw gridline %d\n", grid_n );
192 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
195 grid_n_save = grid_n-1;
197 gdk_cairo_set_source_color(c, &sc2);
198 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
199 cairo_set_line_width(c, 1);
200 for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, 2 * sx, &cimpl); graph_n++)
202 calf_line_graph_draw_graph( c, data, sx, sy );
204 gdk_cairo_set_source_color(c, &sc3);
205 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++)
207 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
208 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
209 cairo_fill(c);
212 // copy window to cache.
213 calf_line_graph_copy_window_to_cache( lg, c );
214 } else {
215 grid_n_save = cache_grid_index;
216 graph_n = cache_graph_index;
217 dot_n = cache_dot_index;
218 calf_line_graph_copy_cache_to_window( lg, c );
222 cairo_set_line_width(c, 1);
223 for(int phase = 1; phase <= 2; phase++)
225 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++)
227 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
231 gdk_cairo_set_source_color(c, &sc2);
232 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
233 cairo_set_line_width(c, 1);
234 for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
236 calf_line_graph_draw_graph( c, data, sx, sy );
238 gdk_cairo_set_source_color(c, &sc3);
239 for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
241 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
242 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
243 cairo_fill(c);
245 delete []data;
248 cairo_destroy(c);
250 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
251 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
253 return TRUE;
256 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
258 g_assert(CALF_IS_LINE_GRAPH(graph));
259 graph->is_square = is_square;
262 static void
263 calf_line_graph_size_request (GtkWidget *widget,
264 GtkRequisition *requisition)
266 g_assert(CALF_IS_LINE_GRAPH(widget));
268 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
271 static void
272 calf_line_graph_size_allocate (GtkWidget *widget,
273 GtkAllocation *allocation)
275 g_assert(CALF_IS_LINE_GRAPH(widget));
276 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
278 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
280 if( lg->cache_surface )
281 cairo_surface_destroy( lg->cache_surface );
282 lg->cache_surface = NULL;
284 widget->allocation = *allocation;
285 GtkAllocation &a = widget->allocation;
286 if (lg->is_square)
288 if (a.width > a.height)
290 a.x += (a.width - a.height) / 2;
291 a.width = a.height;
293 if (a.width < a.height)
295 a.y += (a.height - a.width) / 2;
296 a.height = a.width;
299 parent_class->size_allocate( widget, &a );
302 static void
303 calf_line_graph_class_init (CalfLineGraphClass *klass)
305 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
306 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
307 widget_class->expose_event = calf_line_graph_expose;
308 widget_class->size_request = calf_line_graph_size_request;
309 widget_class->size_allocate = calf_line_graph_size_allocate;
312 static void
313 calf_line_graph_init (CalfLineGraph *self)
315 GtkWidget *widget = GTK_WIDGET(self);
316 widget->requisition.width = 40;
317 widget->requisition.height = 40;
318 self->cache_surface = NULL;
319 self->last_generation = 0;
322 GtkWidget *
323 calf_line_graph_new()
325 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
328 GType
329 calf_line_graph_get_type (void)
331 static GType type = 0;
332 if (!type) {
333 static const GTypeInfo type_info = {
334 sizeof(CalfLineGraphClass),
335 NULL, /* base_init */
336 NULL, /* base_finalize */
337 (GClassInitFunc)calf_line_graph_class_init,
338 NULL, /* class_finalize */
339 NULL, /* class_data */
340 sizeof(CalfLineGraph),
341 0, /* n_preallocs */
342 (GInstanceInitFunc)calf_line_graph_init
345 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
347 for (int i = 0; ; i++) {
348 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
349 if (g_type_from_name(name)) {
350 free(name);
351 continue;
353 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
354 name,
355 type_info_copy,
356 (GTypeFlags)0);
357 free(name);
358 break;
361 return type;
364 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
366 static gboolean
367 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
369 g_assert(CALF_IS_VUMETER(widget));
372 CalfVUMeter *vu = CALF_VUMETER(widget);
373 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
374 int ox = 1, oy = 1;
375 int sx = widget->allocation.width - 2, sy = widget->allocation.height - 2;
377 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
379 GdkColor sc = { 0, 0, 0, 0 };
380 gdk_cairo_set_source_color(c, &sc);
381 cairo_rectangle(c, ox, oy, sx, sy);
382 cairo_fill(c);
383 cairo_set_line_width(c, 1);
385 CalfVUMeterMode mode = vu->mode;
387 for (int x = ox; x <= ox + sx; x += 3)
389 float ts = (x - ox) * 1.0 / sx;
390 float r = 0.f, g = 0.f, b = 0.f;
391 switch(mode)
393 case VU_STANDARD:
394 default:
395 if (ts < 0.75)
396 r = ts / 0.75, g = 1, b = 0;
397 else
398 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
399 if (vu->value < ts || vu->value <= 0)
400 r *= 0.5, g *= 0.5, b *= 0.5;
401 break;
402 case VU_MONOCHROME_REVERSE:
403 r = 1, g = 1, b = 0;
404 if (!(vu->value < ts) || vu->value >= 1.0)
405 r *= 0.5, g *= 0.5, b *= 0.5;
406 break;
407 case VU_MONOCHROME:
408 r = 1, g = 1, b = 0;
409 if (vu->value < ts || vu->value <= 0)
410 r *= 0.5, g *= 0.5, b *= 0.5;
411 break;
413 GdkColor sc2 = { 0, (guint16)(65535 * r), (guint16)(65535 * g), (guint16)(65535 * b) };
414 gdk_cairo_set_source_color(c, &sc2);
415 cairo_move_to(c, x, oy);
416 cairo_line_to(c, x, oy + sy + 1);
417 cairo_move_to(c, x, oy + sy);
418 cairo_line_to(c, x, oy );
419 cairo_stroke(c);
422 cairo_destroy(c);
424 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
425 printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
427 return TRUE;
430 static void
431 calf_vumeter_size_request (GtkWidget *widget,
432 GtkRequisition *requisition)
434 g_assert(CALF_IS_VUMETER(widget));
436 requisition->width = 50;
437 requisition->height = 14;
440 static void
441 calf_vumeter_size_allocate (GtkWidget *widget,
442 GtkAllocation *allocation)
444 g_assert(CALF_IS_VUMETER(widget));
446 widget->allocation = *allocation;
449 static void
450 calf_vumeter_class_init (CalfVUMeterClass *klass)
452 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
453 widget_class->expose_event = calf_vumeter_expose;
454 widget_class->size_request = calf_vumeter_size_request;
455 //widget_class->size_allocate = calf_vumeter_size_allocate;
458 static void
459 calf_vumeter_init (CalfVUMeter *self)
461 GtkWidget *widget = GTK_WIDGET(self);
462 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
463 widget->requisition.width = 50;
464 widget->requisition.height = 15;
465 self->value = 0.5;
468 GtkWidget *
469 calf_vumeter_new()
471 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
474 GType
475 calf_vumeter_get_type (void)
477 static GType type = 0;
478 if (!type) {
479 static const GTypeInfo type_info = {
480 sizeof(CalfVUMeterClass),
481 NULL, /* base_init */
482 NULL, /* base_finalize */
483 (GClassInitFunc)calf_vumeter_class_init,
484 NULL, /* class_finalize */
485 NULL, /* class_data */
486 sizeof(CalfVUMeter),
487 0, /* n_preallocs */
488 (GInstanceInitFunc)calf_vumeter_init
491 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
493 for (int i = 0; ; i++) {
494 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
495 if (g_type_from_name(name)) {
496 free(name);
497 continue;
499 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
500 name,
501 type_info_copy,
502 (GTypeFlags)0);
503 free(name);
504 break;
507 return type;
510 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
512 if (value != meter->value)
514 meter->value = value;
515 gtk_widget_queue_draw(GTK_WIDGET(meter));
519 extern float calf_vumeter_get_value(CalfVUMeter *meter)
521 return meter->value;
524 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
526 if (mode != meter->mode)
528 meter->mode = mode;
529 gtk_widget_queue_draw(GTK_WIDGET(meter));
533 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
535 return meter->mode;
538 ///////////////////////////////////////// knob ///////////////////////////////////////////////
540 static gboolean
541 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
543 g_assert(CALF_IS_KNOB(widget));
545 CalfKnob *self = CALF_KNOB(widget);
546 GdkWindow *window = widget->window;
547 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
549 // printf("adjustment = %p value = %f\n", adj, adj->value);
550 int ox = widget->allocation.x, oy = widget->allocation.y;
552 ox += (widget->allocation.width - 40) / 2;
553 oy += (widget->allocation.height - 40) / 2;
555 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
556 // skip middle phase except for true middle value
557 if (self->knob_type == 1 && phase == 32) {
558 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
559 if (pt < 0)
560 phase = 31;
561 if (pt > 0)
562 phase = 33;
564 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
565 if (self->knob_type == 3 && !(phase % 16)) {
566 if (phase == 64)
567 phase = 0;
568 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
569 double diff = (adj->value - nom) / (adj->upper - adj->lower);
570 if (diff > 0.0001)
571 phase = (phase + 1) % 64;
572 if (diff < -0.0001)
573 phase = (phase + 63) % 64;
575 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);
576 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
577 if (gtk_widget_is_focus(widget))
579 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, 40, 40);
582 return TRUE;
585 static void
586 calf_knob_size_request (GtkWidget *widget,
587 GtkRequisition *requisition)
589 g_assert(CALF_IS_KNOB(widget));
591 // width/height is hardwired at 40px now
592 requisition->width = 40;
593 requisition->height = 40;
596 static void
597 calf_knob_incr (GtkWidget *widget, int dir_down)
599 g_assert(CALF_IS_KNOB(widget));
600 CalfKnob *self = CALF_KNOB(widget);
601 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
603 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
604 int step;
605 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
606 if (dir_down)
607 step = oldstep - 1;
608 else
609 step = oldstep + 1;
610 if (self->knob_type == 3 && step >= nsteps)
611 step %= nsteps;
612 if (self->knob_type == 3 && step < 0)
613 step = nsteps - (nsteps - step) % nsteps;
615 // trying to reduce error cumulation here, by counting from lowest or from highest
616 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
617 gtk_range_set_value(GTK_RANGE(widget), value);
618 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
621 static gboolean
622 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
624 g_assert(CALF_IS_KNOB(widget));
625 CalfKnob *self = CALF_KNOB(widget);
626 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
628 switch(event->keyval)
630 case GDK_Home:
631 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
632 return TRUE;
634 case GDK_End:
635 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
636 return TRUE;
638 case GDK_Up:
639 calf_knob_incr(widget, 0);
640 return TRUE;
642 case GDK_Down:
643 calf_knob_incr(widget, 1);
644 return TRUE;
646 case GDK_Shift_L:
647 case GDK_Shift_R:
648 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
649 self->start_y = self->last_y;
650 return TRUE;
653 return FALSE;
656 static gboolean
657 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
659 g_assert(CALF_IS_KNOB(widget));
660 CalfKnob *self = CALF_KNOB(widget);
662 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
664 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
665 self->start_y = self->last_y;
666 return TRUE;
669 return FALSE;
672 static gboolean
673 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
675 g_assert(CALF_IS_KNOB(widget));
676 CalfKnob *self = CALF_KNOB(widget);
678 // CalfKnob *lg = CALF_KNOB(widget);
679 gtk_widget_grab_focus(widget);
680 gtk_grab_add(widget);
681 self->start_x = event->x;
682 self->start_y = event->y;
683 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
685 return TRUE;
688 static gboolean
689 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
691 g_assert(CALF_IS_KNOB(widget));
693 if (GTK_WIDGET_HAS_GRAB(widget))
694 gtk_grab_remove(widget);
695 return FALSE;
698 static inline float endless(float value)
700 if (value >= 0)
701 return fmod(value, 1.f);
702 else
703 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
706 static inline float deadzone(float value, float incr, float scale)
708 float dzw = 10 / scale;
709 if (value >= 0.501)
710 value += dzw;
711 if (value < 0.499)
712 value -= dzw;
714 value += incr;
716 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
717 return 0.5;
718 if (value < 0.5)
719 return value + dzw;
720 return value - dzw;
723 static gboolean
724 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
726 g_assert(CALF_IS_KNOB(widget));
727 CalfKnob *self = CALF_KNOB(widget);
729 float scale = (event->state & GDK_SHIFT_MASK) ? 1000 : 100;
730 gboolean moved = FALSE;
732 if (GTK_WIDGET_HAS_GRAB(widget))
734 if (self->knob_type == 3)
736 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
738 else
739 if (self->knob_type == 1)
741 gtk_range_set_value(GTK_RANGE(widget), deadzone(self->start_value, -(event->y - self->start_y) / scale, scale));
743 else
745 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
747 moved = TRUE;
749 self->last_y = event->y;
750 return moved;
753 static gboolean
754 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
756 calf_knob_incr(widget, event->direction);
757 return TRUE;
760 static void
761 calf_knob_class_init (CalfKnobClass *klass)
763 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
764 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
765 widget_class->expose_event = calf_knob_expose;
766 widget_class->size_request = calf_knob_size_request;
767 widget_class->button_press_event = calf_knob_button_press;
768 widget_class->button_release_event = calf_knob_button_release;
769 widget_class->motion_notify_event = calf_knob_pointer_motion;
770 widget_class->key_press_event = calf_knob_key_press;
771 widget_class->key_release_event = calf_knob_key_release;
772 widget_class->scroll_event = calf_knob_scroll;
773 GError *error = NULL;
774 klass->knob_image = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob.png", &error);
775 g_assert(klass->knob_image != NULL);
778 static void
779 calf_knob_init (CalfKnob *self)
781 GtkWidget *widget = GTK_WIDGET(self);
782 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
783 widget->requisition.width = 40;
784 widget->requisition.height = 40;
787 GtkWidget *
788 calf_knob_new()
790 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
791 return calf_knob_new_with_adjustment(adj);
794 static gboolean calf_knob_value_changed(gpointer obj)
796 GtkWidget *widget = (GtkWidget *)obj;
797 gtk_widget_queue_draw(widget);
798 return FALSE;
801 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
803 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
804 if (widget) {
805 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
806 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
808 return widget;
811 GType
812 calf_knob_get_type (void)
814 static GType type = 0;
815 if (!type) {
817 static const GTypeInfo type_info = {
818 sizeof(CalfKnobClass),
819 NULL, /* base_init */
820 NULL, /* base_finalize */
821 (GClassInitFunc)calf_knob_class_init,
822 NULL, /* class_finalize */
823 NULL, /* class_data */
824 sizeof(CalfKnob),
825 0, /* n_preallocs */
826 (GInstanceInitFunc)calf_knob_init
829 for (int i = 0; ; i++) {
830 char *name = g_strdup_printf("CalfKnob%u%d",
831 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
832 if (g_type_from_name(name)) {
833 free(name);
834 continue;
836 type = g_type_register_static(GTK_TYPE_RANGE,
837 name,
838 &type_info,
839 (GTypeFlags)0);
840 free(name);
841 break;
844 return type;