Merge branch 'caching'
[calf.git] / src / custom_ctl.cpp
blob035107d55fdccce5b69138b09902f6e89b52c3a2
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 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
194 grid_n_save = grid_n-1;
196 gdk_cairo_set_source_color(c, &sc2);
197 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
198 cairo_set_line_width(c, 1);
199 for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, 2 * sx, &cimpl); graph_n++)
201 calf_line_graph_draw_graph( c, data, sx, sy );
203 gdk_cairo_set_source_color(c, &sc3);
204 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++)
206 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
207 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
208 cairo_fill(c);
211 // copy window to cache.
212 calf_line_graph_copy_window_to_cache( lg, c );
213 } else {
214 grid_n_save = cache_grid_index;
215 graph_n = cache_graph_index;
216 dot_n = cache_dot_index;
217 calf_line_graph_copy_cache_to_window( lg, c );
221 cairo_set_line_width(c, 1);
222 for(int phase = 1; phase <= 2; phase++)
224 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++)
226 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
230 gdk_cairo_set_source_color(c, &sc2);
231 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
232 cairo_set_line_width(c, 1);
233 for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
235 calf_line_graph_draw_graph( c, data, sx, sy );
237 gdk_cairo_set_source_color(c, &sc3);
238 for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
240 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
241 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
242 cairo_fill(c);
244 delete []data;
247 cairo_destroy(c);
249 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
250 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
252 return TRUE;
255 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
257 g_assert(CALF_IS_LINE_GRAPH(graph));
258 graph->is_square = is_square;
261 void calf_line_graph_update_if(CalfLineGraph *graph)
263 g_assert(CALF_IS_LINE_GRAPH(graph));
264 int generation = 0;
265 if (graph->source)
267 int subgraph, dot, gridline;
268 generation = graph->source->get_changed_offsets(graph->last_generation, subgraph, dot, gridline);
269 if (subgraph == INT_MAX && dot == INT_MAX && gridline == INT_MAX)
270 return;
271 gtk_widget_queue_draw(GTK_WIDGET(graph));
275 static void
276 calf_line_graph_size_request (GtkWidget *widget,
277 GtkRequisition *requisition)
279 g_assert(CALF_IS_LINE_GRAPH(widget));
281 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
284 static void
285 calf_line_graph_size_allocate (GtkWidget *widget,
286 GtkAllocation *allocation)
288 g_assert(CALF_IS_LINE_GRAPH(widget));
289 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
291 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
293 if( lg->cache_surface )
294 cairo_surface_destroy( lg->cache_surface );
295 lg->cache_surface = NULL;
297 widget->allocation = *allocation;
298 GtkAllocation &a = widget->allocation;
299 if (lg->is_square)
301 if (a.width > a.height)
303 a.x += (a.width - a.height) / 2;
304 a.width = a.height;
306 if (a.width < a.height)
308 a.y += (a.height - a.width) / 2;
309 a.height = a.width;
312 parent_class->size_allocate( widget, &a );
315 static void
316 calf_line_graph_class_init (CalfLineGraphClass *klass)
318 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
319 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
320 widget_class->expose_event = calf_line_graph_expose;
321 widget_class->size_request = calf_line_graph_size_request;
322 widget_class->size_allocate = calf_line_graph_size_allocate;
325 static void
326 calf_line_graph_init (CalfLineGraph *self)
328 GtkWidget *widget = GTK_WIDGET(self);
329 widget->requisition.width = 40;
330 widget->requisition.height = 40;
331 self->cache_surface = NULL;
332 self->last_generation = 0;
335 GtkWidget *
336 calf_line_graph_new()
338 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
341 GType
342 calf_line_graph_get_type (void)
344 static GType type = 0;
345 if (!type) {
346 static const GTypeInfo type_info = {
347 sizeof(CalfLineGraphClass),
348 NULL, /* base_init */
349 NULL, /* base_finalize */
350 (GClassInitFunc)calf_line_graph_class_init,
351 NULL, /* class_finalize */
352 NULL, /* class_data */
353 sizeof(CalfLineGraph),
354 0, /* n_preallocs */
355 (GInstanceInitFunc)calf_line_graph_init
358 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
360 for (int i = 0; ; i++) {
361 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
362 if (g_type_from_name(name)) {
363 free(name);
364 continue;
366 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
367 name,
368 type_info_copy,
369 (GTypeFlags)0);
370 free(name);
371 break;
374 return type;
377 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
379 static gboolean
380 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
382 g_assert(CALF_IS_VUMETER(widget));
385 CalfVUMeter *vu = CALF_VUMETER(widget);
386 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
387 int ox = 1, oy = 1;
388 int sx = widget->allocation.width - 2, sy = widget->allocation.height - 2;
390 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
392 if( vu->cache_surface == NULL ) {
393 // looks like its either first call or the widget has been resized.
394 // create the cache_surface.
395 cairo_surface_t *window_surface = cairo_get_target( c );
396 vu->cache_surface = cairo_surface_create_similar( window_surface,
397 CAIRO_CONTENT_COLOR,
398 widget->allocation.width,
399 widget->allocation.height );
401 // And render the meterstuff again.
403 cairo_t *cache_cr = cairo_create( vu->cache_surface );
404 GdkColor sc = { 0, 0, 0, 0 };
405 gdk_cairo_set_source_color(cache_cr, &sc);
406 cairo_rectangle(cache_cr, ox, oy, sx, sy);
407 cairo_fill(cache_cr);
408 cairo_set_line_width(cache_cr, 1);
410 for (int x = ox; x <= ox + sx; x += 3)
412 float ts = (x - ox) * 1.0 / sx;
413 float r = 0.f, g = 0.f, b = 0.f;
414 switch(vu->mode)
416 case VU_STANDARD:
417 default:
418 if (ts < 0.75)
419 r = ts / 0.75, g = 1, b = 0;
420 else
421 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
422 // if (vu->value < ts || vu->value <= 0)
423 // r *= 0.5, g *= 0.5, b *= 0.5;
424 break;
425 case VU_MONOCHROME_REVERSE:
426 r = 1, g = 1, b = 0;
427 // if (!(vu->value < ts) || vu->value >= 1.0)
428 // r *= 0.5, g *= 0.5, b *= 0.5;
429 break;
430 case VU_MONOCHROME:
431 r = 1, g = 1, b = 0;
432 // if (vu->value < ts || vu->value <= 0)
433 // r *= 0.5, g *= 0.5, b *= 0.5;
434 break;
436 GdkColor sc2 = { 0, (guint16)(65535 * r), (guint16)(65535 * g), (guint16)(65535 * b) };
437 gdk_cairo_set_source_color(cache_cr, &sc2);
438 cairo_move_to(cache_cr, x, oy);
439 cairo_line_to(cache_cr, x, oy + sy + 1);
440 cairo_move_to(cache_cr, x, oy + sy);
441 cairo_line_to(cache_cr, x, oy );
442 cairo_stroke(cache_cr);
444 cairo_destroy( cache_cr );
447 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
448 cairo_paint( c );
449 cairo_set_source_rgba( c, 0,0,0, 0.5 );
451 if( vu->mode == VU_MONOCHROME_REVERSE )
452 cairo_rectangle( c, ox,oy, vu->value * (sx-ox) + 1, sy-oy+1 );
453 else
454 cairo_rectangle( c, ox + vu->value * (sx-ox), oy, sx-ox+1, sy-oy+1 );
456 cairo_fill( c );
460 cairo_destroy(c);
462 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
463 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
465 return TRUE;
468 static void
469 calf_vumeter_size_request (GtkWidget *widget,
470 GtkRequisition *requisition)
472 g_assert(CALF_IS_VUMETER(widget));
474 requisition->width = 50;
475 requisition->height = 14;
478 static void
479 calf_vumeter_size_allocate (GtkWidget *widget,
480 GtkAllocation *allocation)
482 g_assert(CALF_IS_VUMETER(widget));
483 CalfVUMeter *vu = CALF_VUMETER(widget);
485 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
487 parent_class->size_allocate( widget, allocation );
489 if( vu->cache_surface )
490 cairo_surface_destroy( vu->cache_surface );
491 vu->cache_surface = NULL;
494 static void
495 calf_vumeter_class_init (CalfVUMeterClass *klass)
497 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
498 widget_class->expose_event = calf_vumeter_expose;
499 widget_class->size_request = calf_vumeter_size_request;
500 widget_class->size_allocate = calf_vumeter_size_allocate;
503 static void
504 calf_vumeter_init (CalfVUMeter *self)
506 GtkWidget *widget = GTK_WIDGET(self);
507 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
508 widget->requisition.width = 50;
509 widget->requisition.height = 15;
510 self->value = 0.5;
511 self->cache_surface = NULL;
514 GtkWidget *
515 calf_vumeter_new()
517 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
520 GType
521 calf_vumeter_get_type (void)
523 static GType type = 0;
524 if (!type) {
525 static const GTypeInfo type_info = {
526 sizeof(CalfVUMeterClass),
527 NULL, /* base_init */
528 NULL, /* base_finalize */
529 (GClassInitFunc)calf_vumeter_class_init,
530 NULL, /* class_finalize */
531 NULL, /* class_data */
532 sizeof(CalfVUMeter),
533 0, /* n_preallocs */
534 (GInstanceInitFunc)calf_vumeter_init
537 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
539 for (int i = 0; ; i++) {
540 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
541 if (g_type_from_name(name)) {
542 free(name);
543 continue;
545 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
546 name,
547 type_info_copy,
548 (GTypeFlags)0);
549 free(name);
550 break;
553 return type;
556 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
558 if (value != meter->value)
560 meter->value = value;
561 gtk_widget_queue_draw(GTK_WIDGET(meter));
565 extern float calf_vumeter_get_value(CalfVUMeter *meter)
567 return meter->value;
570 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
572 if (mode != meter->mode)
574 meter->mode = mode;
575 gtk_widget_queue_draw(GTK_WIDGET(meter));
579 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
581 return meter->mode;
584 ///////////////////////////////////////// knob ///////////////////////////////////////////////
586 static gboolean
587 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
589 g_assert(CALF_IS_KNOB(widget));
591 CalfKnob *self = CALF_KNOB(widget);
592 GdkWindow *window = widget->window;
593 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
595 // printf("adjustment = %p value = %f\n", adj, adj->value);
596 int ox = widget->allocation.x, oy = widget->allocation.y;
598 ox += (widget->allocation.width - 40) / 2;
599 oy += (widget->allocation.height - 40) / 2;
601 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
602 // skip middle phase except for true middle value
603 if (self->knob_type == 1 && phase == 32) {
604 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
605 if (pt < 0)
606 phase = 31;
607 if (pt > 0)
608 phase = 33;
610 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
611 if (self->knob_type == 3 && !(phase % 16)) {
612 if (phase == 64)
613 phase = 0;
614 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
615 double diff = (adj->value - nom) / (adj->upper - adj->lower);
616 if (diff > 0.0001)
617 phase = (phase + 1) % 64;
618 if (diff < -0.0001)
619 phase = (phase + 63) % 64;
621 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);
622 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
623 if (gtk_widget_is_focus(widget))
625 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, 40, 40);
628 return TRUE;
631 static void
632 calf_knob_size_request (GtkWidget *widget,
633 GtkRequisition *requisition)
635 g_assert(CALF_IS_KNOB(widget));
637 // width/height is hardwired at 40px now
638 requisition->width = 40;
639 requisition->height = 40;
642 static void
643 calf_knob_incr (GtkWidget *widget, int dir_down)
645 g_assert(CALF_IS_KNOB(widget));
646 CalfKnob *self = CALF_KNOB(widget);
647 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
649 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
650 int step;
651 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
652 if (dir_down)
653 step = oldstep - 1;
654 else
655 step = oldstep + 1;
656 if (self->knob_type == 3 && step >= nsteps)
657 step %= nsteps;
658 if (self->knob_type == 3 && step < 0)
659 step = nsteps - (nsteps - step) % nsteps;
661 // trying to reduce error cumulation here, by counting from lowest or from highest
662 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
663 gtk_range_set_value(GTK_RANGE(widget), value);
664 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
667 static gboolean
668 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
670 g_assert(CALF_IS_KNOB(widget));
671 CalfKnob *self = CALF_KNOB(widget);
672 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
674 switch(event->keyval)
676 case GDK_Home:
677 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
678 return TRUE;
680 case GDK_End:
681 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
682 return TRUE;
684 case GDK_Up:
685 calf_knob_incr(widget, 0);
686 return TRUE;
688 case GDK_Down:
689 calf_knob_incr(widget, 1);
690 return TRUE;
692 case GDK_Shift_L:
693 case GDK_Shift_R:
694 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
695 self->start_y = self->last_y;
696 return TRUE;
699 return FALSE;
702 static gboolean
703 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
705 g_assert(CALF_IS_KNOB(widget));
706 CalfKnob *self = CALF_KNOB(widget);
708 if(event->keyval == GDK_Shift_L || event->keyval == 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_button_press (GtkWidget *widget, GdkEventButton *event)
721 g_assert(CALF_IS_KNOB(widget));
722 CalfKnob *self = CALF_KNOB(widget);
724 // CalfKnob *lg = CALF_KNOB(widget);
725 gtk_widget_grab_focus(widget);
726 gtk_grab_add(widget);
727 self->start_x = event->x;
728 self->start_y = event->y;
729 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
731 return TRUE;
734 static gboolean
735 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
737 g_assert(CALF_IS_KNOB(widget));
739 if (GTK_WIDGET_HAS_GRAB(widget))
740 gtk_grab_remove(widget);
741 return FALSE;
744 static inline float endless(float value)
746 if (value >= 0)
747 return fmod(value, 1.f);
748 else
749 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
752 static inline float deadzone(float value, float incr, float scale)
754 float dzw = 10 / scale;
755 if (value >= 0.501)
756 value += dzw;
757 if (value < 0.499)
758 value -= dzw;
760 value += incr;
762 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
763 return 0.5;
764 if (value < 0.5)
765 return value + dzw;
766 return value - dzw;
769 static gboolean
770 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
772 g_assert(CALF_IS_KNOB(widget));
773 CalfKnob *self = CALF_KNOB(widget);
775 float scale = (event->state & GDK_SHIFT_MASK) ? 1000 : 100;
776 gboolean moved = FALSE;
778 if (GTK_WIDGET_HAS_GRAB(widget))
780 if (self->knob_type == 3)
782 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
784 else
785 if (self->knob_type == 1)
787 gtk_range_set_value(GTK_RANGE(widget), deadzone(self->start_value, -(event->y - self->start_y) / scale, scale));
789 else
791 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
793 moved = TRUE;
795 self->last_y = event->y;
796 return moved;
799 static gboolean
800 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
802 calf_knob_incr(widget, event->direction);
803 return TRUE;
806 static void
807 calf_knob_class_init (CalfKnobClass *klass)
809 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
810 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
811 widget_class->expose_event = calf_knob_expose;
812 widget_class->size_request = calf_knob_size_request;
813 widget_class->button_press_event = calf_knob_button_press;
814 widget_class->button_release_event = calf_knob_button_release;
815 widget_class->motion_notify_event = calf_knob_pointer_motion;
816 widget_class->key_press_event = calf_knob_key_press;
817 widget_class->key_release_event = calf_knob_key_release;
818 widget_class->scroll_event = calf_knob_scroll;
819 GError *error = NULL;
820 klass->knob_image = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob.png", &error);
821 g_assert(klass->knob_image != NULL);
824 static void
825 calf_knob_init (CalfKnob *self)
827 GtkWidget *widget = GTK_WIDGET(self);
828 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
829 widget->requisition.width = 40;
830 widget->requisition.height = 40;
833 GtkWidget *
834 calf_knob_new()
836 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
837 return calf_knob_new_with_adjustment(adj);
840 static gboolean calf_knob_value_changed(gpointer obj)
842 GtkWidget *widget = (GtkWidget *)obj;
843 gtk_widget_queue_draw(widget);
844 return FALSE;
847 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
849 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
850 if (widget) {
851 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
852 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
854 return widget;
857 GType
858 calf_knob_get_type (void)
860 static GType type = 0;
861 if (!type) {
863 static const GTypeInfo type_info = {
864 sizeof(CalfKnobClass),
865 NULL, /* base_init */
866 NULL, /* base_finalize */
867 (GClassInitFunc)calf_knob_class_init,
868 NULL, /* class_finalize */
869 NULL, /* class_data */
870 sizeof(CalfKnob),
871 0, /* n_preallocs */
872 (GInstanceInitFunc)calf_knob_init
875 for (int i = 0; ; i++) {
876 char *name = g_strdup_printf("CalfKnob%u%d",
877 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
878 if (g_type_from_name(name)) {
879 free(name);
880 continue;
882 type = g_type_register_static(GTK_TYPE_RANGE,
883 name,
884 &type_info,
885 (GTypeFlags)0);
886 free(name);
887 break;
890 return type;