+ Framework fix dssi crash bug in line_graph expose
[calf.git] / src / custom_ctl.cpp
blobc119edee37cc2dd323c45137841746ef02760473
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;
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 static void
262 calf_line_graph_size_request (GtkWidget *widget,
263 GtkRequisition *requisition)
265 g_assert(CALF_IS_LINE_GRAPH(widget));
267 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
270 static void
271 calf_line_graph_size_allocate (GtkWidget *widget,
272 GtkAllocation *allocation)
274 g_assert(CALF_IS_LINE_GRAPH(widget));
275 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
277 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
279 if( lg->cache_surface )
280 cairo_surface_destroy( lg->cache_surface );
281 lg->cache_surface = NULL;
283 widget->allocation = *allocation;
284 GtkAllocation &a = widget->allocation;
285 if (lg->is_square)
287 if (a.width > a.height)
289 a.x += (a.width - a.height) / 2;
290 a.width = a.height;
292 if (a.width < a.height)
294 a.y += (a.height - a.width) / 2;
295 a.height = a.width;
298 parent_class->size_allocate( widget, &a );
301 static void
302 calf_line_graph_class_init (CalfLineGraphClass *klass)
304 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
305 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
306 widget_class->expose_event = calf_line_graph_expose;
307 widget_class->size_request = calf_line_graph_size_request;
308 widget_class->size_allocate = calf_line_graph_size_allocate;
311 static void
312 calf_line_graph_init (CalfLineGraph *self)
314 GtkWidget *widget = GTK_WIDGET(self);
315 widget->requisition.width = 40;
316 widget->requisition.height = 40;
317 self->cache_surface = NULL;
318 self->last_generation = 0;
321 GtkWidget *
322 calf_line_graph_new()
324 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
327 GType
328 calf_line_graph_get_type (void)
330 static GType type = 0;
331 if (!type) {
332 static const GTypeInfo type_info = {
333 sizeof(CalfLineGraphClass),
334 NULL, /* base_init */
335 NULL, /* base_finalize */
336 (GClassInitFunc)calf_line_graph_class_init,
337 NULL, /* class_finalize */
338 NULL, /* class_data */
339 sizeof(CalfLineGraph),
340 0, /* n_preallocs */
341 (GInstanceInitFunc)calf_line_graph_init
344 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
346 for (int i = 0; ; i++) {
347 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
348 if (g_type_from_name(name)) {
349 free(name);
350 continue;
352 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
353 name,
354 type_info_copy,
355 (GTypeFlags)0);
356 free(name);
357 break;
360 return type;
363 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
365 static gboolean
366 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
368 g_assert(CALF_IS_VUMETER(widget));
371 CalfVUMeter *vu = CALF_VUMETER(widget);
372 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
373 int ox = 1, oy = 1;
374 int sx = widget->allocation.width - 2, sy = widget->allocation.height - 2;
376 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
378 if( vu->cache_surface == NULL ) {
379 // looks like its either first call or the widget has been resized.
380 // create the cache_surface.
381 cairo_surface_t *window_surface = cairo_get_target( c );
382 vu->cache_surface = cairo_surface_create_similar( window_surface,
383 CAIRO_CONTENT_COLOR,
384 widget->allocation.width,
385 widget->allocation.height );
387 // And render the meterstuff again.
389 cairo_t *cache_cr = cairo_create( vu->cache_surface );
390 GdkColor sc = { 0, 0, 0, 0 };
391 gdk_cairo_set_source_color(cache_cr, &sc);
392 cairo_rectangle(cache_cr, ox, oy, sx, sy);
393 cairo_fill(cache_cr);
394 cairo_set_line_width(cache_cr, 1);
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(vu->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(cache_cr, &sc2);
424 cairo_move_to(cache_cr, x, oy);
425 cairo_line_to(cache_cr, x, oy + sy + 1);
426 cairo_move_to(cache_cr, x, oy + sy);
427 cairo_line_to(cache_cr, x, oy );
428 cairo_stroke(cache_cr);
430 cairo_destroy( cache_cr );
433 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
434 cairo_paint( c );
435 cairo_set_source_rgba( c, 0,0,0, 0.5 );
437 if( vu->mode == VU_MONOCHROME_REVERSE )
438 cairo_rectangle( c, ox,oy, vu->value * (sx-ox) + 1, sy-oy+1 );
439 else
440 cairo_rectangle( c, ox + vu->value * (sx-ox), oy, sx-ox+1, sy-oy+1 );
442 cairo_fill( c );
446 cairo_destroy(c);
448 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 1, oy - 1, sx + 2, sy + 2);
449 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
451 return TRUE;
454 static void
455 calf_vumeter_size_request (GtkWidget *widget,
456 GtkRequisition *requisition)
458 g_assert(CALF_IS_VUMETER(widget));
460 requisition->width = 50;
461 requisition->height = 14;
464 static void
465 calf_vumeter_size_allocate (GtkWidget *widget,
466 GtkAllocation *allocation)
468 g_assert(CALF_IS_VUMETER(widget));
469 CalfVUMeter *vu = CALF_VUMETER(widget);
471 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
473 parent_class->size_allocate( widget, allocation );
475 if( vu->cache_surface )
476 cairo_surface_destroy( vu->cache_surface );
477 vu->cache_surface = NULL;
480 static void
481 calf_vumeter_class_init (CalfVUMeterClass *klass)
483 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
484 widget_class->expose_event = calf_vumeter_expose;
485 widget_class->size_request = calf_vumeter_size_request;
486 widget_class->size_allocate = calf_vumeter_size_allocate;
489 static void
490 calf_vumeter_init (CalfVUMeter *self)
492 GtkWidget *widget = GTK_WIDGET(self);
493 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
494 widget->requisition.width = 50;
495 widget->requisition.height = 15;
496 self->value = 0.5;
497 self->cache_surface = NULL;
500 GtkWidget *
501 calf_vumeter_new()
503 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
506 GType
507 calf_vumeter_get_type (void)
509 static GType type = 0;
510 if (!type) {
511 static const GTypeInfo type_info = {
512 sizeof(CalfVUMeterClass),
513 NULL, /* base_init */
514 NULL, /* base_finalize */
515 (GClassInitFunc)calf_vumeter_class_init,
516 NULL, /* class_finalize */
517 NULL, /* class_data */
518 sizeof(CalfVUMeter),
519 0, /* n_preallocs */
520 (GInstanceInitFunc)calf_vumeter_init
523 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
525 for (int i = 0; ; i++) {
526 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
527 if (g_type_from_name(name)) {
528 free(name);
529 continue;
531 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
532 name,
533 type_info_copy,
534 (GTypeFlags)0);
535 free(name);
536 break;
539 return type;
542 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
544 if (value != meter->value)
546 meter->value = value;
547 gtk_widget_queue_draw(GTK_WIDGET(meter));
551 extern float calf_vumeter_get_value(CalfVUMeter *meter)
553 return meter->value;
556 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
558 if (mode != meter->mode)
560 meter->mode = mode;
561 gtk_widget_queue_draw(GTK_WIDGET(meter));
565 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
567 return meter->mode;
570 ///////////////////////////////////////// knob ///////////////////////////////////////////////
572 static gboolean
573 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
575 g_assert(CALF_IS_KNOB(widget));
577 CalfKnob *self = CALF_KNOB(widget);
578 GdkWindow *window = widget->window;
579 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
581 // printf("adjustment = %p value = %f\n", adj, adj->value);
582 int ox = widget->allocation.x, oy = widget->allocation.y;
584 ox += (widget->allocation.width - 40) / 2;
585 oy += (widget->allocation.height - 40) / 2;
587 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
588 // skip middle phase except for true middle value
589 if (self->knob_type == 1 && phase == 32) {
590 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
591 if (pt < 0)
592 phase = 31;
593 if (pt > 0)
594 phase = 33;
596 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
597 if (self->knob_type == 3 && !(phase % 16)) {
598 if (phase == 64)
599 phase = 0;
600 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
601 double diff = (adj->value - nom) / (adj->upper - adj->lower);
602 if (diff > 0.0001)
603 phase = (phase + 1) % 64;
604 if (diff < -0.0001)
605 phase = (phase + 63) % 64;
607 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);
608 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
609 if (gtk_widget_is_focus(widget))
611 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, 40, 40);
614 return TRUE;
617 static void
618 calf_knob_size_request (GtkWidget *widget,
619 GtkRequisition *requisition)
621 g_assert(CALF_IS_KNOB(widget));
623 // width/height is hardwired at 40px now
624 requisition->width = 40;
625 requisition->height = 40;
628 static void
629 calf_knob_incr (GtkWidget *widget, int dir_down)
631 g_assert(CALF_IS_KNOB(widget));
632 CalfKnob *self = CALF_KNOB(widget);
633 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
635 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
636 int step;
637 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
638 if (dir_down)
639 step = oldstep - 1;
640 else
641 step = oldstep + 1;
642 if (self->knob_type == 3 && step >= nsteps)
643 step %= nsteps;
644 if (self->knob_type == 3 && step < 0)
645 step = nsteps - (nsteps - step) % nsteps;
647 // trying to reduce error cumulation here, by counting from lowest or from highest
648 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
649 gtk_range_set_value(GTK_RANGE(widget), value);
650 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
653 static gboolean
654 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
656 g_assert(CALF_IS_KNOB(widget));
657 CalfKnob *self = CALF_KNOB(widget);
658 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
660 switch(event->keyval)
662 case GDK_Home:
663 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
664 return TRUE;
666 case GDK_End:
667 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
668 return TRUE;
670 case GDK_Up:
671 calf_knob_incr(widget, 0);
672 return TRUE;
674 case GDK_Down:
675 calf_knob_incr(widget, 1);
676 return TRUE;
678 case GDK_Shift_L:
679 case GDK_Shift_R:
680 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
681 self->start_y = self->last_y;
682 return TRUE;
685 return FALSE;
688 static gboolean
689 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
691 g_assert(CALF_IS_KNOB(widget));
692 CalfKnob *self = CALF_KNOB(widget);
694 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
696 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
697 self->start_y = self->last_y;
698 return TRUE;
701 return FALSE;
704 static gboolean
705 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
707 g_assert(CALF_IS_KNOB(widget));
708 CalfKnob *self = CALF_KNOB(widget);
710 // CalfKnob *lg = CALF_KNOB(widget);
711 gtk_widget_grab_focus(widget);
712 gtk_grab_add(widget);
713 self->start_x = event->x;
714 self->start_y = event->y;
715 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
717 return TRUE;
720 static gboolean
721 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
723 g_assert(CALF_IS_KNOB(widget));
725 if (GTK_WIDGET_HAS_GRAB(widget))
726 gtk_grab_remove(widget);
727 return FALSE;
730 static inline float endless(float value)
732 if (value >= 0)
733 return fmod(value, 1.f);
734 else
735 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
738 static inline float deadzone(float value, float incr, float scale)
740 float dzw = 10 / scale;
741 if (value >= 0.501)
742 value += dzw;
743 if (value < 0.499)
744 value -= dzw;
746 value += incr;
748 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
749 return 0.5;
750 if (value < 0.5)
751 return value + dzw;
752 return value - dzw;
755 static gboolean
756 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
758 g_assert(CALF_IS_KNOB(widget));
759 CalfKnob *self = CALF_KNOB(widget);
761 float scale = (event->state & GDK_SHIFT_MASK) ? 1000 : 100;
762 gboolean moved = FALSE;
764 if (GTK_WIDGET_HAS_GRAB(widget))
766 if (self->knob_type == 3)
768 gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
770 else
771 if (self->knob_type == 1)
773 gtk_range_set_value(GTK_RANGE(widget), deadzone(self->start_value, -(event->y - self->start_y) / scale, scale));
775 else
777 gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
779 moved = TRUE;
781 self->last_y = event->y;
782 return moved;
785 static gboolean
786 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
788 calf_knob_incr(widget, event->direction);
789 return TRUE;
792 static void
793 calf_knob_class_init (CalfKnobClass *klass)
795 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
796 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
797 widget_class->expose_event = calf_knob_expose;
798 widget_class->size_request = calf_knob_size_request;
799 widget_class->button_press_event = calf_knob_button_press;
800 widget_class->button_release_event = calf_knob_button_release;
801 widget_class->motion_notify_event = calf_knob_pointer_motion;
802 widget_class->key_press_event = calf_knob_key_press;
803 widget_class->key_release_event = calf_knob_key_release;
804 widget_class->scroll_event = calf_knob_scroll;
805 GError *error = NULL;
806 klass->knob_image = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob.png", &error);
807 g_assert(klass->knob_image != NULL);
810 static void
811 calf_knob_init (CalfKnob *self)
813 GtkWidget *widget = GTK_WIDGET(self);
814 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
815 widget->requisition.width = 40;
816 widget->requisition.height = 40;
819 GtkWidget *
820 calf_knob_new()
822 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
823 return calf_knob_new_with_adjustment(adj);
826 static gboolean calf_knob_value_changed(gpointer obj)
828 GtkWidget *widget = (GtkWidget *)obj;
829 gtk_widget_queue_draw(widget);
830 return FALSE;
833 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
835 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
836 if (widget) {
837 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
838 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
840 return widget;
843 GType
844 calf_knob_get_type (void)
846 static GType type = 0;
847 if (!type) {
849 static const GTypeInfo type_info = {
850 sizeof(CalfKnobClass),
851 NULL, /* base_init */
852 NULL, /* base_finalize */
853 (GClassInitFunc)calf_knob_class_init,
854 NULL, /* class_finalize */
855 NULL, /* class_data */
856 sizeof(CalfKnob),
857 0, /* n_preallocs */
858 (GInstanceInitFunc)calf_knob_init
861 for (int i = 0; ; i++) {
862 char *name = g_strdup_printf("CalfKnob%u%d",
863 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
864 if (g_type_from_name(name)) {
865 free(name);
866 continue;
868 type = g_type_register_static(GTK_TYPE_RANGE,
869 name,
870 &type_info,
871 (GTypeFlags)0);
872 free(name);
873 break;
876 return type;