+ DSP primitives: fix a rather stupid bug in clamping functions
[calf.git] / src / custom_ctl.cpp
blob16fc21c00a52597d974059092a0c5d33c4d17303
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., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
22 #include "config.h"
23 #include <calf/custom_ctl.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #include <math.h>
27 #include <gdk/gdk.h>
30 I don't really know how to do it, or if it can be done this way.
31 struct calf_ui_type_module
33 GTypeModule *module;
35 calf_ui_type_module()
37 module = g_type_module_new();
38 g_type_module_set_name(module, "calf_custom_ctl");
39 g_type_module_use(module);
41 ~calf_ui_type_module()
43 g_type_module_unuse(module);
47 static calf_ui_type_module type_module;
50 static void
51 calf_line_graph_copy_cache_to_window( CalfLineGraph *lg, cairo_t *c )
53 cairo_save( c );
54 cairo_set_source_surface( c, lg->cache_surface, 0,0 );
55 cairo_paint( c );
56 cairo_restore( c );
59 static void
60 calf_line_graph_copy_window_to_cache( CalfLineGraph *lg, cairo_t *c )
62 cairo_t *cache_cr = cairo_create( lg->cache_surface );
63 cairo_surface_t *window_surface = cairo_get_target( c );
64 cairo_set_source_surface( cache_cr, window_surface, 0,0 );
65 cairo_paint( cache_cr );
66 cairo_destroy( cache_cr );
69 static void
70 calf_line_graph_draw_grid( cairo_t *c, std::string &legend, bool vertical, float pos, int phase, int sx, int sy )
72 int ox=2, oy=2;
73 cairo_text_extents_t tx;
74 if (!legend.empty())
75 cairo_text_extents(c, legend.c_str(), &tx);
76 if (vertical)
78 float x = floor(ox + pos * sx) + 0.5;
79 if (phase == 1)
81 cairo_move_to(c, x, oy);
82 cairo_line_to(c, x, oy + sy);
83 cairo_stroke(c);
85 if (phase == 2 && !legend.empty()) {
87 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 0.7);
88 cairo_move_to(c, x - (tx.x_bearing + tx.width / 2.0) - 2, oy + sy - 2);
89 cairo_show_text(c, legend.c_str());
92 else
94 float y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
95 if (phase == 1)
97 cairo_move_to(c, ox, y);
98 cairo_line_to(c, ox + sx, y);
99 cairo_stroke(c);
101 if (phase == 2 && !legend.empty()) {
102 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 0.7);
103 cairo_move_to(c, ox + sx - 4 - tx.width, y + tx.height/2);
104 cairo_show_text(c, legend.c_str());
109 static void
110 calf_line_graph_draw_graph( cairo_t *c, float *data, int sx, int sy )
112 int ox=2, oy=2;
114 for (int i = 0; i < 2 * sx; i++)
116 int y = (int)(oy + sy / 2 - (sy / 2 - 1) * data[i]);
117 //if (y < oy) y = oy;
118 //if (y >= oy + sy) y = oy + sy - 1;
119 if (i)
120 cairo_line_to(c, ox + i * 0.5, y);
121 else
122 cairo_move_to(c, ox, y);
124 cairo_stroke(c);
127 static gboolean
128 calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
130 g_assert(CALF_IS_LINE_GRAPH(widget));
132 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
133 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
134 int ox = 2, oy = 2;
135 int sx = widget->allocation.width - 2, sy = widget->allocation.height - 2;
137 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
138 GtkStyle *style;
139 style = gtk_widget_get_style(widget);
140 GdkColor sc = { 0, 0, 0, 0 };
142 bool cache_dirty = 0;
144 if( lg->cache_surface == NULL ) {
145 // looks like its either first call or the widget has been resized.
146 // create the cache_surface.
147 cairo_surface_t *window_surface = cairo_get_target( c );
148 lg->cache_surface = cairo_surface_create_similar( window_surface,
149 CAIRO_CONTENT_COLOR,
150 widget->allocation.width,
151 widget->allocation.height );
152 //cairo_set_source_surface( cache_cr, window_surface, 0,0 );
153 //cairo_paint( cache_cr );
155 cache_dirty = 1;
158 cairo_select_font_face(c, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
159 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.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
174 float x, y;
175 int size = 0;
176 GdkColor sc3 = { 0, 0.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
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->source_id, 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 cairo_pattern_t *pt = cairo_pattern_create_linear(ox, oy, ox, sy);
190 cairo_pattern_add_color_stop_rgb(pt, 0.0, 0.69, 0.79, 0.35);
191 cairo_pattern_add_color_stop_rgb(pt, 0.025, 0.84, 0.94, 0.49);
192 cairo_pattern_add_color_stop_rgb(pt, 0.5, 0.78, 0.89, 0.45);
193 cairo_pattern_add_color_stop_rgb(pt, 0.500001,0.76, 0.87, 0.38);
194 cairo_pattern_add_color_stop_rgb(pt, 1.0, 0.89, 1.00, 0.45);
195 //gdk_cairo_set_source_color(cache_cr, &sc);
196 cairo_set_source (cache_cr, pt);
197 cairo_rectangle(cache_cr, ox, oy, sx, sy);
198 cairo_clip_preserve(cache_cr);
199 cairo_fill_preserve(cache_cr);
201 // cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.5);
202 // cairo_set_line_width(cache_cr, 1);
203 // cairo_stroke(cache_cr);
205 cairo_impl cache_cimpl;
206 cache_cimpl.context = cache_cr;
208 lg->source->get_changed_offsets( lg->source_id, gen_index, cache_graph_index, cache_dot_index, cache_grid_index );
209 lg->last_generation = gen_index;
211 cairo_set_line_width(cache_cr, 1);
212 for(int phase = 1; phase <= 2; phase++)
214 for(grid_n = 0; legend = std::string(), cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.6), (grid_n<cache_grid_index) && lg->source->get_gridline(lg->source_id, grid_n, pos, vertical, legend, &cache_cimpl); grid_n++)
216 calf_line_graph_draw_grid( cache_cr, legend, vertical, pos, phase, sx, sy );
219 grid_n_save = grid_n;
221 gdk_cairo_set_source_color(cache_cr, &sc2);
222 cairo_set_line_join(cache_cr, CAIRO_LINE_JOIN_MITER);
223 cairo_set_line_width(cache_cr, 1);
224 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++)
226 calf_line_graph_draw_graph( cache_cr, data, sx, sy );
228 gdk_cairo_set_source_color(cache_cr, &sc3);
229 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++)
231 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
232 cairo_arc(cache_cr, ox + x * sx, yv, size, 0, 2 * M_PI);
233 cairo_fill(cache_cr);
236 // copy window to cache.
237 cairo_destroy( cache_cr );
238 calf_line_graph_copy_cache_to_window( lg, c );
239 } else {
240 grid_n_save = cache_grid_index;
241 graph_n = cache_graph_index;
242 dot_n = cache_dot_index;
243 calf_line_graph_copy_cache_to_window( lg, c );
247 cairo_set_line_width(c, 1);
248 for(int phase = 1; phase <= 2; phase++)
250 for(int gn=grid_n_save; legend = std::string(), cairo_set_source_rgba(c, 0, 0, 0, 0.6), lg->source->get_gridline(lg->source_id, gn, pos, vertical, legend, &cimpl); gn++)
252 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
256 gdk_cairo_set_source_color(c, &sc2);
257 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
258 cairo_set_line_width(c, 1);
259 for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
261 calf_line_graph_draw_graph( c, data, sx, sy );
263 gdk_cairo_set_source_color(c, &sc3);
264 for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
266 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
267 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
268 cairo_fill(c);
270 delete []data;
273 cairo_destroy(c);
275 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
276 // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
278 return TRUE;
281 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
283 g_assert(CALF_IS_LINE_GRAPH(graph));
284 graph->is_square = is_square;
287 int calf_line_graph_update_if(CalfLineGraph *graph, int last_drawn_generation)
289 g_assert(CALF_IS_LINE_GRAPH(graph));
290 int generation = last_drawn_generation;
291 if (graph->source)
293 int subgraph, dot, gridline;
294 generation = graph->source->get_changed_offsets(graph->source_id, generation, subgraph, dot, gridline);
295 if (subgraph == INT_MAX && dot == INT_MAX && gridline == INT_MAX && generation == last_drawn_generation)
296 return generation;
297 gtk_widget_queue_draw(GTK_WIDGET(graph));
299 return generation;
302 static void
303 calf_line_graph_size_request (GtkWidget *widget,
304 GtkRequisition *requisition)
306 g_assert(CALF_IS_LINE_GRAPH(widget));
308 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
311 static void
312 calf_line_graph_size_allocate (GtkWidget *widget,
313 GtkAllocation *allocation)
315 g_assert(CALF_IS_LINE_GRAPH(widget));
316 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
318 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
320 if( lg->cache_surface )
321 cairo_surface_destroy( lg->cache_surface );
322 lg->cache_surface = NULL;
324 widget->allocation = *allocation;
325 GtkAllocation &a = widget->allocation;
326 if (lg->is_square)
328 if (a.width > a.height)
330 a.x += (a.width - a.height) / 2;
331 a.width = a.height;
333 if (a.width < a.height)
335 a.y += (a.height - a.width) / 2;
336 a.height = a.width;
339 parent_class->size_allocate( widget, &a );
342 static void
343 calf_line_graph_class_init (CalfLineGraphClass *klass)
345 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
346 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
347 widget_class->expose_event = calf_line_graph_expose;
348 widget_class->size_request = calf_line_graph_size_request;
349 widget_class->size_allocate = calf_line_graph_size_allocate;
352 static void
353 calf_line_graph_init (CalfLineGraph *self)
355 GtkWidget *widget = GTK_WIDGET(self);
356 widget->requisition.width = 40;
357 widget->requisition.height = 40;
358 self->cache_surface = NULL;
359 self->last_generation = 0;
362 GtkWidget *
363 calf_line_graph_new()
365 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
368 GType
369 calf_line_graph_get_type (void)
371 static GType type = 0;
372 if (!type) {
373 static const GTypeInfo type_info = {
374 sizeof(CalfLineGraphClass),
375 NULL, /* base_init */
376 NULL, /* base_finalize */
377 (GClassInitFunc)calf_line_graph_class_init,
378 NULL, /* class_finalize */
379 NULL, /* class_data */
380 sizeof(CalfLineGraph),
381 0, /* n_preallocs */
382 (GInstanceInitFunc)calf_line_graph_init
385 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
387 for (int i = 0; ; i++) {
388 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
389 if (g_type_from_name(name)) {
390 free(name);
391 continue;
393 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
394 name,
395 type_info_copy,
396 (GTypeFlags)0);
397 free(name);
398 break;
401 return type;
404 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
406 static gboolean
407 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
409 g_assert(CALF_IS_VUMETER(widget));
412 CalfVUMeter *vu = CALF_VUMETER(widget);
413 GtkStyle *style;
414 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
415 int ox = 2, oy = 2;
416 // if only 1 px border:
417 // int sx = widget->allocation.width - 1 - ((widget->allocation.width - 2) % 3), sy = widget->allocation.height - 2;
418 int sx = widget->allocation.width - 4 - ((widget->allocation.width - 2) % 3), sy = widget->allocation.height - 4;
419 style = gtk_widget_get_style(widget);
420 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
422 if( vu->cache_surface == NULL ) {
423 // looks like its either first call or the widget has been resized.
424 // create the cache_surface.
425 cairo_surface_t *window_surface = cairo_get_target( c );
426 vu->cache_surface = cairo_surface_create_similar( window_surface,
427 CAIRO_CONTENT_COLOR,
428 widget->allocation.width,
429 widget->allocation.height );
431 // And render the meterstuff again.
433 cairo_t *cache_cr = cairo_create( vu->cache_surface );
434 GdkColor sc = { 0, 0, 0, 0 };
435 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
436 cairo_paint(cache_cr);
437 gdk_cairo_set_source_color(cache_cr, &sc);
438 cairo_rectangle(cache_cr, ox, oy, sx, sy);
439 cairo_fill(cache_cr);
440 cairo_set_line_width(cache_cr, 1);
442 for (int x = ox + 1; x <= ox + sx - 3; x += 3)
444 float ts = (x - ox) * 1.0 / sx;
445 float r = 0.f, g = 0.f, b = 0.f;
446 switch(vu->mode)
448 case VU_STANDARD:
449 default:
450 if (ts < 0.75)
451 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
452 else
453 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
454 // if (vu->value < ts || vu->value <= 0)
455 // r *= 0.5, g *= 0.5, b *= 0.5;
456 break;
457 case VU_MONOCHROME_REVERSE:
458 r = 0, g = 170.0 / 255.0, b = 1;
459 // if (!(vu->value < ts) || vu->value >= 1.0)
460 // r *= 0.5, g *= 0.5, b *= 0.5;
461 break;
462 case VU_MONOCHROME:
463 r = 0, g = 170.0 / 255.0, b = 1;
464 // if (vu->value < ts || vu->value <= 0)
465 // r *= 0.5, g *= 0.5, b *= 0.5;
466 break;
468 GdkColor sc2 = { 0, (guint16)(65535 * r + 0.2), (guint16)(65535 * g), (guint16)(65535 * b) };
469 GdkColor sc3 = { 0, (guint16)(65535 * r * 0.7), (guint16)(65535 * g * 0.7), (guint16)(65535 * b * 0.7) };
470 gdk_cairo_set_source_color(cache_cr, &sc2);
471 cairo_move_to(cache_cr, x + 0.5, oy + 1);
472 cairo_line_to(cache_cr, x + 0.5, oy + sy - 1);
473 cairo_stroke(cache_cr);
474 gdk_cairo_set_source_color(cache_cr, &sc3);
475 cairo_move_to(cache_cr, x + 1.5, oy + sy - 1);
476 cairo_line_to(cache_cr, x + 1.5, oy + 1);
477 cairo_stroke(cache_cr);
479 cairo_destroy( cache_cr );
482 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
483 cairo_paint( c );
484 cairo_set_source_rgba( c, 0,0,0, 0.6 );
486 float value = vu->value > 1.f ? 1.f : vu->value;
487 if( vu->mode == VU_MONOCHROME_REVERSE )
488 cairo_rectangle( c, ox + 1,oy + 1, value * (sx - 2), sy - 2);
489 else
490 cairo_rectangle( c, ox + 1 + value * (sx - 2), oy + 1, (sx - 2) * (1 - value), sy - 2 );
492 cairo_fill( c );
494 cairo_destroy(c);
496 gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
497 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
499 return TRUE;
502 static void
503 calf_vumeter_size_request (GtkWidget *widget,
504 GtkRequisition *requisition)
506 g_assert(CALF_IS_VUMETER(widget));
508 requisition->width = 50;
509 requisition->height = 16;
512 static void
513 calf_vumeter_size_allocate (GtkWidget *widget,
514 GtkAllocation *allocation)
516 g_assert(CALF_IS_VUMETER(widget));
517 CalfVUMeter *vu = CALF_VUMETER(widget);
519 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
521 parent_class->size_allocate( widget, allocation );
523 if( vu->cache_surface )
524 cairo_surface_destroy( vu->cache_surface );
525 vu->cache_surface = NULL;
528 static void
529 calf_vumeter_class_init (CalfVUMeterClass *klass)
531 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
532 widget_class->expose_event = calf_vumeter_expose;
533 widget_class->size_request = calf_vumeter_size_request;
534 widget_class->size_allocate = calf_vumeter_size_allocate;
537 static void
538 calf_vumeter_init (CalfVUMeter *self)
540 GtkWidget *widget = GTK_WIDGET(self);
541 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
542 widget->requisition.width = 50;
543 widget->requisition.height = 16;
544 self->value = 0.5;
545 self->cache_surface = NULL;
548 GtkWidget *
549 calf_vumeter_new()
551 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
554 GType
555 calf_vumeter_get_type (void)
557 static GType type = 0;
558 if (!type) {
559 static const GTypeInfo type_info = {
560 sizeof(CalfVUMeterClass),
561 NULL, /* base_init */
562 NULL, /* base_finalize */
563 (GClassInitFunc)calf_vumeter_class_init,
564 NULL, /* class_finalize */
565 NULL, /* class_data */
566 sizeof(CalfVUMeter),
567 0, /* n_preallocs */
568 (GInstanceInitFunc)calf_vumeter_init
571 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
573 for (int i = 0; ; i++) {
574 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
575 if (g_type_from_name(name)) {
576 free(name);
577 continue;
579 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
580 name,
581 type_info_copy,
582 (GTypeFlags)0);
583 free(name);
584 break;
587 return type;
590 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
592 if (value != meter->value)
594 meter->value = value;
595 gtk_widget_queue_draw(GTK_WIDGET(meter));
599 extern float calf_vumeter_get_value(CalfVUMeter *meter)
601 return meter->value;
604 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
606 if (mode != meter->mode)
608 meter->mode = mode;
609 gtk_widget_queue_draw(GTK_WIDGET(meter));
613 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
615 return meter->mode;
618 ///////////////////////////////////////// knob ///////////////////////////////////////////////
620 static gboolean
621 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
623 g_assert(CALF_IS_KNOB(widget));
625 CalfKnob *self = CALF_KNOB(widget);
626 GdkWindow *window = widget->window;
627 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
629 // printf("adjustment = %p value = %f\n", adj, adj->value);
630 int ox = widget->allocation.x, oy = widget->allocation.y;
632 ox += (widget->allocation.width - self->knob_size * 20) / 2;
633 oy += (widget->allocation.height - self->knob_size * 20) / 2;
635 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
636 // skip middle phase except for true middle value
637 if (self->knob_type == 1 && phase == 32) {
638 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
639 if (pt < 0)
640 phase = 31;
641 if (pt > 0)
642 phase = 33;
644 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
645 if (self->knob_type == 3 && !(phase % 16)) {
646 if (phase == 64)
647 phase = 0;
648 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
649 double diff = (adj->value - nom) / (adj->upper - adj->lower);
650 if (diff > 0.0001)
651 phase = (phase + 1) % 64;
652 if (diff < -0.0001)
653 phase = (phase + 63) % 64;
655 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget))->knob_image[self->knob_size - 1], phase * self->knob_size * 20, self->knob_type * self->knob_size * 20, ox, oy, self->knob_size * 20, self->knob_size * 20, GDK_RGB_DITHER_NORMAL, 0, 0);
656 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
657 if (gtk_widget_is_focus(widget))
659 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, self->knob_size * 20, self->knob_size * 20);
662 return TRUE;
665 static void
666 calf_knob_size_request (GtkWidget *widget,
667 GtkRequisition *requisition)
669 g_assert(CALF_IS_KNOB(widget));
671 CalfKnob *self = CALF_KNOB(widget);
673 // width/height is hardwired at 40px now
674 // is now chooseable by "size" value in XML (1-4)
675 requisition->width = 20 * self->knob_size;
676 requisition->height = 20 * self->knob_size;
679 static void
680 calf_knob_incr (GtkWidget *widget, int dir_down)
682 g_assert(CALF_IS_KNOB(widget));
683 CalfKnob *self = CALF_KNOB(widget);
684 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
686 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
687 int step;
688 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
689 if (dir_down)
690 step = oldstep - 1;
691 else
692 step = oldstep + 1;
693 if (self->knob_type == 3 && step >= nsteps)
694 step %= nsteps;
695 if (self->knob_type == 3 && step < 0)
696 step = nsteps - (nsteps - step) % nsteps;
698 // trying to reduce error cumulation here, by counting from lowest or from highest
699 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
700 gtk_range_set_value(GTK_RANGE(widget), value);
701 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
704 static gboolean
705 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
707 g_assert(CALF_IS_KNOB(widget));
708 CalfKnob *self = CALF_KNOB(widget);
709 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
711 switch(event->keyval)
713 case GDK_Home:
714 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
715 return TRUE;
717 case GDK_End:
718 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
719 return TRUE;
721 case GDK_Up:
722 calf_knob_incr(widget, 0);
723 return TRUE;
725 case GDK_Down:
726 calf_knob_incr(widget, 1);
727 return TRUE;
729 case GDK_Shift_L:
730 case GDK_Shift_R:
731 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
732 self->start_y = self->last_y;
733 return TRUE;
736 return FALSE;
739 static gboolean
740 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
742 g_assert(CALF_IS_KNOB(widget));
743 CalfKnob *self = CALF_KNOB(widget);
745 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
747 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
748 self->start_y = self->last_y;
749 return TRUE;
752 return FALSE;
755 static gboolean
756 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
758 g_assert(CALF_IS_KNOB(widget));
759 CalfKnob *self = CALF_KNOB(widget);
761 // CalfKnob *lg = CALF_KNOB(widget);
762 gtk_widget_grab_focus(widget);
763 gtk_grab_add(widget);
764 self->start_x = event->x;
765 self->start_y = event->y;
766 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
768 return TRUE;
771 static gboolean
772 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
774 g_assert(CALF_IS_KNOB(widget));
776 if (GTK_WIDGET_HAS_GRAB(widget))
777 gtk_grab_remove(widget);
778 return FALSE;
781 static inline float endless(float value)
783 if (value >= 0)
784 return fmod(value, 1.f);
785 else
786 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
789 static inline float deadzone(float value, float incr, float scale)
791 float dzw = 10 / scale;
792 if (value >= 0.501)
793 value += dzw;
794 if (value < 0.499)
795 value -= dzw;
797 value += incr;
799 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
800 return 0.5;
801 if (value < 0.5)
802 return value + dzw;
803 return value - dzw;
806 static gboolean
807 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
809 g_assert(CALF_IS_KNOB(widget));
810 CalfKnob *self = CALF_KNOB(widget);
812 float scale = (event->state & GDK_SHIFT_MASK) ? 10 : 1;
813 gboolean moved = FALSE;
815 if (GTK_WIDGET_HAS_GRAB(widget))
817 float sens=1/(75*(1+fabs((self->start_x - event->x)/10)));
818 if (self->knob_type == 3)
820 gtk_range_set_value(GTK_RANGE(widget), endless(gtk_range_get_value(GTK_RANGE(widget)) - (event->y - self->last_y) / scale * sens));
822 // else
823 // if (self->knob_type == 1)
824 // {
825 // gtk_range_set_value(GTK_RANGE(widget), deadzone(gtk_range_get_value(GTK_RANGE(widget)), -(event->y - self->last_y) / scale * sens, scale * sens));
826 // }
827 else
829 gtk_range_set_value(GTK_RANGE(widget), gtk_range_get_value(GTK_RANGE(widget)) - (event->y - self->last_y) / scale * sens);
831 moved = TRUE;
833 self->last_y = event->y;
834 self->last_x = event->x;
835 return moved;
838 static gboolean
839 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
841 calf_knob_incr(widget, event->direction);
842 return TRUE;
845 static void
846 calf_knob_class_init (CalfKnobClass *klass)
848 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
849 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
850 widget_class->expose_event = calf_knob_expose;
851 widget_class->size_request = calf_knob_size_request;
852 widget_class->button_press_event = calf_knob_button_press;
853 widget_class->button_release_event = calf_knob_button_release;
854 widget_class->motion_notify_event = calf_knob_pointer_motion;
855 widget_class->key_press_event = calf_knob_key_press;
856 widget_class->key_release_event = calf_knob_key_release;
857 widget_class->scroll_event = calf_knob_scroll;
858 GError *error = NULL;
859 klass->knob_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob1.png", &error);
860 klass->knob_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob2.png", &error);
861 klass->knob_image[2] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob3.png", &error);
862 klass->knob_image[3] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob4.png", &error);
863 g_assert(klass->knob_image != NULL);
866 static void
867 calf_knob_init (CalfKnob *self)
869 GtkWidget *widget = GTK_WIDGET(self);
870 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
871 widget->requisition.width = 40;
872 widget->requisition.height = 40;
875 GtkWidget *
876 calf_knob_new()
878 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
879 return calf_knob_new_with_adjustment(adj);
882 static gboolean calf_knob_value_changed(gpointer obj)
884 GtkWidget *widget = (GtkWidget *)obj;
885 gtk_widget_queue_draw(widget);
886 return FALSE;
889 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
891 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
892 if (widget) {
893 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
894 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
896 return widget;
899 GType
900 calf_knob_get_type (void)
902 static GType type = 0;
903 if (!type) {
905 static const GTypeInfo type_info = {
906 sizeof(CalfKnobClass),
907 NULL, /* base_init */
908 NULL, /* base_finalize */
909 (GClassInitFunc)calf_knob_class_init,
910 NULL, /* class_finalize */
911 NULL, /* class_data */
912 sizeof(CalfKnob),
913 0, /* n_preallocs */
914 (GInstanceInitFunc)calf_knob_init
917 for (int i = 0; ; i++) {
918 char *name = g_strdup_printf("CalfKnob%u%d",
919 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
920 if (g_type_from_name(name)) {
921 free(name);
922 continue;
924 type = g_type_register_static(GTK_TYPE_RANGE,
925 name,
926 &type_info,
927 (GTypeFlags)0);
928 free(name);
929 break;
932 return type;
935 ///////////////////////////////////////// toggle ///////////////////////////////////////////////
937 static gboolean
938 calf_toggle_expose (GtkWidget *widget, GdkEventExpose *event)
940 g_assert(CALF_IS_TOGGLE(widget));
942 CalfToggle *self = CALF_TOGGLE(widget);
943 GdkWindow *window = widget->window;
945 int ox = widget->allocation.x, oy = widget->allocation.y;
946 int width = self->size * 30, height = self->size * 20;
948 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], CALF_TOGGLE_CLASS(GTK_OBJECT_GET_CLASS(widget))->toggle_image[self->size - 1], 0, height * gtk_range_get_value(GTK_RANGE(widget)), ox, oy, width, height, GDK_RGB_DITHER_NORMAL, 0, 0);
949 if (gtk_widget_is_focus(widget))
951 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, width, height);
954 return TRUE;
957 static void
958 calf_toggle_size_request (GtkWidget *widget,
959 GtkRequisition *requisition)
961 g_assert(CALF_IS_TOGGLE(widget));
963 CalfToggle *self = CALF_TOGGLE(widget);
965 requisition->width = 30 * self->size;
966 requisition->height = 20 * self->size;
969 static gboolean
970 calf_toggle_button_press (GtkWidget *widget, GdkEventButton *event)
972 g_assert(CALF_IS_TOGGLE(widget));
973 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
974 if (gtk_range_get_value(GTK_RANGE(widget)) == adj->lower)
976 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
977 } else {
978 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
980 return TRUE;
983 static gboolean
984 calf_toggle_key_press (GtkWidget *widget, GdkEventKey *event)
986 switch(event->keyval)
988 case GDK_Return:
989 case GDK_KP_Enter:
990 case GDK_space:
991 return calf_toggle_button_press(widget, NULL);
993 return FALSE;
996 static void
997 calf_toggle_class_init (CalfToggleClass *klass)
999 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1000 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1001 widget_class->expose_event = calf_toggle_expose;
1002 widget_class->size_request = calf_toggle_size_request;
1003 widget_class->button_press_event = calf_toggle_button_press;
1004 widget_class->key_press_event = calf_toggle_key_press;
1005 GError *error = NULL;
1006 klass->toggle_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/toggle1.png", &error);
1007 klass->toggle_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/toggle2.png", &error);
1008 g_assert(klass->toggle_image != NULL);
1011 static void
1012 calf_toggle_init (CalfToggle *self)
1014 GtkWidget *widget = GTK_WIDGET(self);
1015 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
1016 widget->requisition.width = 30;
1017 widget->requisition.height = 20;
1018 self->size = 1;
1021 GtkWidget *
1022 calf_toggle_new()
1024 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 1, 0, 0);
1025 return calf_toggle_new_with_adjustment(adj);
1028 static gboolean calf_toggle_value_changed(gpointer obj)
1030 GtkWidget *widget = (GtkWidget *)obj;
1031 gtk_widget_queue_draw(widget);
1032 return FALSE;
1035 GtkWidget *calf_toggle_new_with_adjustment(GtkAdjustment *_adjustment)
1037 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TOGGLE, NULL ));
1038 if (widget) {
1039 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
1040 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_toggle_value_changed), widget);
1042 return widget;
1045 GType
1046 calf_toggle_get_type (void)
1048 static GType type = 0;
1049 if (!type) {
1051 static const GTypeInfo type_info = {
1052 sizeof(CalfToggleClass),
1053 NULL, /* base_init */
1054 NULL, /* base_finalize */
1055 (GClassInitFunc)calf_toggle_class_init,
1056 NULL, /* class_finalize */
1057 NULL, /* class_data */
1058 sizeof(CalfToggle),
1059 0, /* n_preallocs */
1060 (GInstanceInitFunc)calf_toggle_init
1063 for (int i = 0; ; i++) {
1064 char *name = g_strdup_printf("CalfToggle%u%d",
1065 ((unsigned int)(intptr_t)calf_toggle_class_init) >> 16, i);
1066 if (g_type_from_name(name)) {
1067 free(name);
1068 continue;
1070 type = g_type_register_static( GTK_TYPE_RANGE,
1071 name,
1072 &type_info,
1073 (GTypeFlags)0);
1074 free(name);
1075 break;
1078 return type;