1 /* Calf DSP Library
2  * Knob control.
3  * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
4  * and others
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  */
21 #include "config.h"
22 #include <calf/ctl_knob.h>
23 #include <calf/drawingutils.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #include <math.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <gdk/gdk.h>
30 #include <algorithm>
31 #include <stdlib.h>
32 
33 #define range01(tick) std::min(1., std::max(0., tick))
34 
35 ///////////////////////////////////////// knob ///////////////////////////////////////////////
36 
37 static void
calf_knob_get_color(CalfKnob * self,float deg,float phase,float start,float last,float tickw,float * r,float * g,float * b,float * a)38 calf_knob_get_color (CalfKnob *self, float deg, float phase, float start, float last, float tickw, float *r, float *g, float *b, float *a)
39 {
40     GtkStateType state = GTK_STATE_NORMAL;
41     GtkWidget *widget = GTK_WIDGET(self);
42 
43     //printf ("get color: phase %.2f deg %.2f\n", phase, deg);
44     if (self->type == 0) {
45         // normal
46         if (!(deg > phase or phase == start))
47             state = GTK_STATE_PRELIGHT;
48     }
49     if (self->type == 1) {
50         // centered
51         if (deg > 270 and deg <= phase and phase > 270)
52             state = GTK_STATE_PRELIGHT;
53         if (deg <= 270 and deg > phase and phase < 270)
54             state = GTK_STATE_PRELIGHT;
55         if ((deg == start and phase == start)
56         or  (deg == 270.  and phase > 270.))
57             state = GTK_STATE_PRELIGHT;
58     }
59     if (self->type == 2) {
60         // reverse
61         if (deg > phase or phase == start)
62             state = GTK_STATE_PRELIGHT;
63     }
64     if (self->type == 3) {
65         for (unsigned j = 0; j < self->ticks.size(); j++) {
66             float tp = fmod((start + range01(self->ticks[j]) * 360.) - phase + 360, 360);
67             if (tp > 360 - tickw or tp < tickw) {
68                 state = GTK_STATE_PRELIGHT;
69             }
70         }
71         if (deg > phase and deg > last + tickw and last < phase)
72             state = GTK_STATE_PRELIGHT;
73 
74     }
75     get_fg_color(widget, &state, r, g, b);
76     if (state == GTK_STATE_NORMAL)
77         gtk_widget_style_get(widget, "alpha-normal", a, NULL);
78     else
79         gtk_widget_style_get(widget, "alpha-prelight", a, NULL);
80 
81 }
82 
83 static gboolean
calf_knob_expose(GtkWidget * widget,GdkEventExpose * event)84 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
85 {
86     g_assert(CALF_IS_KNOB(widget));
87     CalfKnob *self = CALF_KNOB(widget);
88 
89     if (!self->knob_image)
90         return FALSE;
91 
92     GdkPixbuf *pixbuf = self->knob_image;
93     gint iw = gdk_pixbuf_get_width(pixbuf);
94     gint ih = gdk_pixbuf_get_height(pixbuf);
95 
96     if (self->debug > 1)
97         printf("pixbuf: %d x %d\n", iw, ih);
98 
99     GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
100     cairo_t *ctx = gdk_cairo_create(GDK_DRAWABLE(widget->window));
101 
102     float r, g, b;
103     GtkStateType state;
104 
105     float rmargin, rwidth, tmargin, twidth, tlength, flw;
106     gtk_widget_style_get(widget, "ring-margin", &rmargin,
107                                  "ring-width",  &rwidth,
108                                  "tick-margin", &tmargin,
109                                  "tick-width",  &twidth,
110                                  "tick-length", &tlength,
111                                  "focus-line-width", &flw, NULL);
112 
113     if (self->debug > 1)
114         printf("gtkrc: rm %.2f | rw %.2f | tm %.2f | tw %.2f | tl %.2f\n", rmargin, rwidth, tmargin, twidth, tlength);
115 
116     double ox   = widget->allocation.x + (widget->allocation.width - iw) / 2;
117     double oy   = widget->allocation.y + (widget->allocation.height - ih) / 2;
118     double size = iw;
119     float  rad  = size / 2;
120     double xc   = ox + rad;
121     double yc   = oy + rad;
122 
123     if (self->debug > 1)
124         printf("position: %.2f x %.2f\n", ox, oy);
125 
126     unsigned int tick;
127     double phase;
128     double base;
129     double deg;
130     double end;
131     double last;
132     double start;
133     double nend;
134     double zero;
135     float opac = 0;
136 
137     double perim  = (rad - rmargin) * 2 * M_PI;
138     double tickw  = 2. / perim * 360.;
139     double tickw2 = tickw / 2.;
140 
141     cairo_rectangle(ctx, ox, oy, size + size / 2, size + size / 2);
142     cairo_clip(ctx);
143 
144     // draw background
145     gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], pixbuf,
146                     0, 0, ox, oy, iw, ih, GDK_RGB_DITHER_NORMAL, 0, 0);
147 
148     switch (self->type) {
149         default:
150         case 0:
151             // normal knob
152             start = 135.;
153             end   = 405.;
154             base  = 270.;
155             zero  = 135.;
156         case 1:
157             // centered @ 270°
158             start = 135.;
159             end   = 405.;
160             base  = 270.;
161             zero  = 270.;
162         case 2:
163             // reversed
164             start = 135.;
165             end   = 405.;
166             base  = 270.;
167             zero  = 135.;
168             break;
169         case 3:
170             // 360°
171             start = -90.;
172             end   = 270.;
173             base  = 360.;
174             zero  = -90.;
175             break;
176     }
177     tick  = 0;
178     nend  = 0.;
179     deg = last = start;
180     phase = (adj->value - adj->lower) * base / (adj->upper - adj->lower) + start;
181 
182     // draw pin
183     state = GTK_STATE_ACTIVE;
184     get_fg_color(widget, &state, &r, &g, &b);
185     float x1 = ox + rad + (rad - tmargin) * cos(phase * (M_PI / 180.));
186     float y1 = oy + rad + (rad - tmargin) * sin(phase * (M_PI / 180.));
187     float x2 = ox + rad + (rad - tlength - tmargin) * cos(phase * (M_PI / 180.));
188     float y2 = oy + rad + (rad - tlength - tmargin) * sin(phase * (M_PI / 180.));
189     cairo_move_to(ctx, x1, y1);
190     cairo_line_to(ctx, x2, y2);
191     cairo_set_source_rgba(ctx, r, g, b, 1);
192     cairo_set_line_width(ctx, twidth);
193     cairo_stroke(ctx);
194 
195     if (self->debug > 1)
196         printf("pin color: %.2f | %.2f | %.2f\n", r, g, b);
197 
198     cairo_set_line_width(ctx, rwidth);
199 
200     // draw ticks and rings
201     state = GTK_STATE_NORMAL;
202     get_fg_color(widget, &state, &r, &g, &b);
203     unsigned int evsize = 4;
204     double events[4] = { start, zero, end, phase };
205     if (self->type == 3)
206         evsize = 3;
207     std::sort(events, events + evsize);
208     if (self->debug) {
209         printf("start %.2f end %.2f last %.2f deg %.2f tick %d ticks %d phase %.2f base %.2f nend %.2f\n", start, end, last, deg, tick, int(self->ticks.size()), phase, base, nend);
210         for (unsigned int i = 0; i < self->ticks.size(); i++) {
211             printf("tick %d %.2f\n", i, self->ticks[i]);
212         }
213     }
214     while (deg <= end) {
215         if (self->debug) printf("tick %d deg %.2f last %.2f end %.2f\n", tick, deg, last, end);
216         if (self->ticks.size() and tick < self->ticks.size() and deg == start + range01(self->ticks[tick]) * base) {
217             // seems we want to draw a tick on this angle.
218             // so we have to fill the void between the last set angle
219             // and the point directly before the tick first.
220             // (draw from last known angle to tickw2 + tickw before actual deg)
221             if (last < deg - tickw - tickw2) {
222                 calf_knob_get_color(self, (deg - tickw - tickw2), phase, start, last, tickw + tickw2, &r, &g, &b, &opac);
223                 cairo_set_source_rgba(ctx, r, g, b, opac);
224                 cairo_arc(ctx, xc, yc, rad - rmargin, last * (M_PI / 180.), std::max(last, std::min(nend, (deg - tickw - tickw2))) * (M_PI / 180.));
225                 cairo_stroke(ctx);
226                 if (self->debug) printf("fill from %.2f to %.2f @ %.2f\n", last, (deg - tickw - tickw2), opac);
227                 if (self->debug > 1)
228                     printf("color: %.2f | %.2f | %.2f\n", r, g, b);
229             }
230             // draw the tick itself
231             calf_knob_get_color(self, deg, phase, start, end, tickw + tickw2, &r, &g, &b, &opac);
232             cairo_set_source_rgba(ctx, r, g, b, opac);
233             cairo_arc(ctx, xc, yc, rad - rmargin, (deg - tickw2) * (M_PI / 180.), (deg + tickw2) * (M_PI / 180.));
234             cairo_stroke(ctx);
235             if (self->debug) printf("tick from %.2f to %.2f @ %.2f\n", (deg - tickw2), (deg + tickw2), opac);
236             if (self->debug > 1)
237                 printf("color: %.2f | %.2f | %.2f\n", r, g, b);
238             // set last known angle to deg plus tickw + tickw2
239             last = deg + tickw + tickw2;
240             // and count up tick
241             tick ++;
242             // remember the next ticks void end
243             if (tick < self->ticks.size())
244                 nend = range01(self->ticks[tick]) * base + start - tickw - tickw2;
245             else
246                 nend = end;
247         } else {
248             // seems we want to fill a gap between the last event and
249             // the actual one, while the actual one isn't a tick (but a
250             // knobs position or a center)
251             if ((last < deg)) {
252                 calf_knob_get_color(self, deg, phase, start, last, tickw + tickw2, &r, &g, &b, &opac);
253                 cairo_set_source_rgba(ctx, r, g, b, opac);
254                 cairo_arc(ctx, xc, yc, rad - rmargin, last * (M_PI / 180.), std::min(nend, std::max(last, deg)) * (M_PI / 180.));
255                 cairo_stroke(ctx);
256                 if (self->debug) printf("void from %.2f to %.2f @ %.2f\n", last, std::min(nend, std::max(last, deg)), opac);
257                 if (self->debug > 1)
258                     printf("color: %.2f | %.2f | %.2f\n", r, g, b);
259             }
260             last = deg;
261         }
262         if (deg >= end)
263             break;
264         // set deg to next event
265         for (unsigned int i = 0; i < evsize; i++) {
266             if (self->debug > 1) printf("checking %.2f (start %.2f zero %.2f phase %.2f end %.2f)\n", events[i], start, zero, phase, end);
267             if (events[i] > deg) {
268                 deg = events[i];
269                 if (self->debug > 1) printf("taken.\n");
270                 break;
271             }
272         }
273         if (tick < self->ticks.size()) {
274             deg = std::min(deg, start + range01(self->ticks[tick]) * base);
275             if (self->debug > 1) printf("checking tick %d %.2f\n", tick, start + range01(self->ticks[tick]) * base);
276         }
277         //deg = std::max(last, deg);
278         if (self->debug > 1) printf("finally! deg %.2f\n", deg);
279     }
280     if (self->debug) printf("\n");
281     cairo_destroy(ctx);
282     return TRUE;
283 }
284 
285 static void
calf_knob_size_request(GtkWidget * widget,GtkRequisition * requisition)286 calf_knob_size_request (GtkWidget *widget,
287                            GtkRequisition *requisition)
288 {
289     g_assert(CALF_IS_KNOB(widget));
290     CalfKnob *self = CALF_KNOB(widget);
291     if (!self->knob_image)
292         return;
293     requisition->width  = gdk_pixbuf_get_width(self->knob_image);
294     requisition->height = gdk_pixbuf_get_height(self->knob_image);
295 }
296 
297 void
calf_knob_set_size(CalfKnob * self,int size)298 calf_knob_set_size (CalfKnob *self, int size)
299 {
300     char name[128];
301     GtkWidget *widget = GTK_WIDGET(self);
302     self->size = size;
303     sprintf(name, "%s_%d\n", gtk_widget_get_name(widget), size);
304     gtk_widget_set_name(widget, name);
305     gtk_widget_queue_resize(widget);
306 }
307 
308 void
calf_knob_set_pixbuf(CalfKnob * self,GdkPixbuf * pixbuf)309 calf_knob_set_pixbuf (CalfKnob *self, GdkPixbuf *pixbuf)
310 {
311     self->knob_image = pixbuf;
312     gtk_widget_queue_resize(GTK_WIDGET(self));
313 }
314 
calf_knob_enter(GtkWidget * widget,GdkEventCrossing * ev)315 static gboolean calf_knob_enter (GtkWidget *widget, GdkEventCrossing* ev)
316 {
317     if (gtk_widget_get_state(widget) == GTK_STATE_NORMAL) {
318         gtk_widget_set_state(widget, GTK_STATE_PRELIGHT);
319         gtk_widget_queue_draw(widget);
320     }
321     return TRUE;
322 }
323 
calf_knob_leave(GtkWidget * widget,GdkEventCrossing * ev)324 static gboolean calf_knob_leave (GtkWidget *widget, GdkEventCrossing *ev)
325 {
326     if (gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT) {
327         gtk_widget_set_state(widget, GTK_STATE_NORMAL);
328         gtk_widget_queue_draw(widget);
329     }
330     return TRUE;
331 }
332 
333 static void
calf_knob_incr(GtkWidget * widget,int dir_down)334 calf_knob_incr (GtkWidget *widget, int dir_down)
335 {
336     g_assert(CALF_IS_KNOB(widget));
337     CalfKnob *self = CALF_KNOB(widget);
338     GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
339 
340     int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
341     int step;
342     int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
343     if (dir_down)
344         step = oldstep - 1;
345     else
346         step = oldstep + 1;
347     if (self->type == 3 && step >= nsteps)
348         step %= nsteps;
349     if (self->type == 3 && step < 0)
350         step = nsteps - (nsteps - step) % nsteps;
351 
352     // trying to reduce error cumulation here, by counting from lowest or from highest
353     float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
354     gtk_range_set_value(GTK_RANGE(widget), value);
355     // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
356 }
357 
358 static gboolean
calf_knob_key_press(GtkWidget * widget,GdkEventKey * event)359 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
360 {
361     g_assert(CALF_IS_KNOB(widget));
362     CalfKnob *self = CALF_KNOB(widget);
363     GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
364     gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
365     gtk_widget_queue_draw(widget);
366     switch(event->keyval)
367     {
368         case GDK_Home:
369             gtk_range_set_value(GTK_RANGE(widget), adj->lower);
370             return TRUE;
371 
372         case GDK_End:
373             gtk_range_set_value(GTK_RANGE(widget), adj->upper);
374             return TRUE;
375 
376         case GDK_Up:
377             calf_knob_incr(widget, 0);
378             return TRUE;
379 
380         case GDK_Down:
381             calf_knob_incr(widget, 1);
382             return TRUE;
383 
384         case GDK_Shift_L:
385         case GDK_Shift_R:
386             self->start_value = gtk_range_get_value(GTK_RANGE(widget));
387             self->start_y = self->last_y;
388             return TRUE;
389     }
390 
391     return FALSE;
392 }
393 
394 static gboolean
calf_knob_key_release(GtkWidget * widget,GdkEventKey * event)395 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
396 {
397     g_assert(CALF_IS_KNOB(widget));
398     CalfKnob *self = CALF_KNOB(widget);
399 
400     if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
401     {
402         self->start_value = gtk_range_get_value(GTK_RANGE(widget));
403         self->start_y = self->last_y;
404         return TRUE;
405     }
406     gtk_widget_set_state(widget, GTK_STATE_NORMAL);
407     gtk_widget_queue_draw(widget);
408     return FALSE;
409 }
410 
411 static gboolean
calf_knob_button_press(GtkWidget * widget,GdkEventButton * event)412 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
413 {
414     g_assert(CALF_IS_KNOB(widget));
415     CalfKnob *self = CALF_KNOB(widget);
416 
417     if (event->type == GDK_2BUTTON_PRESS) {
418         gtk_range_set_value(GTK_RANGE(widget), self->default_value);
419     }
420 
421     // CalfKnob *lg = CALF_KNOB(widget);
422     gtk_widget_grab_focus(widget);
423     gtk_grab_add(widget);
424     self->start_x = event->x;
425     self->last_y = self->start_y = event->y;
426     self->start_value = gtk_range_get_value(GTK_RANGE(widget));
427     gtk_widget_set_state(widget, GTK_STATE_ACTIVE);
428     gtk_widget_queue_draw(widget);
429     return TRUE;
430 }
431 
432 static gboolean
calf_knob_button_release(GtkWidget * widget,GdkEventButton * event)433 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
434 {
435     g_assert(CALF_IS_KNOB(widget));
436 
437     if (GTK_WIDGET_HAS_GRAB(widget))
438         gtk_grab_remove(widget);
439     gtk_widget_set_state(widget, GTK_STATE_NORMAL);
440     gtk_widget_queue_draw(widget);
441     return FALSE;
442 }
443 
endless(float value)444 static inline float endless(float value)
445 {
446     if (value >= 0)
447         return fmod(value, 1.f);
448     else
449         return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
450 }
451 
deadzone(GtkWidget * widget,float value,float incr)452 static inline float deadzone(GtkWidget *widget, float value, float incr)
453 {
454     // map to dead zone
455     float ov = value;
456     if (ov > 0.5)
457         ov = 0.1 + ov;
458     if (ov < 0.5)
459         ov = ov - 0.1;
460 
461     float nv = ov + incr;
462 
463     if (nv > 0.6)
464         return nv - 0.1;
465     if (nv < 0.4)
466         return nv + 0.1;
467     return 0.5;
468 }
469 
470 static gboolean
calf_knob_pointer_motion(GtkWidget * widget,GdkEventMotion * event)471 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
472 {
473     g_assert(CALF_IS_KNOB(widget));
474     CalfKnob *self = CALF_KNOB(widget);
475 
476     float scale = (event->state & GDK_SHIFT_MASK) ? 2500 : 250;
477     gboolean moved = FALSE;
478 
479     if (GTK_WIDGET_HAS_GRAB(widget))
480     {
481         if (self->type == 3)
482         {
483             gtk_range_set_value(GTK_RANGE(widget), endless(self->start_value - (event->y - self->start_y) / scale));
484         }
485         else
486         if (self->type == 1)
487         {
488             gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), self->start_value, -(event->y - self->start_y) / scale));
489         }
490         else
491         {
492             gtk_range_set_value(GTK_RANGE(widget), self->start_value - (event->y - self->start_y) / scale);
493         }
494         moved = TRUE;
495     }
496     self->last_y = event->y;
497     return moved;
498 }
499 
500 static gboolean
calf_knob_scroll(GtkWidget * widget,GdkEventScroll * event)501 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
502 {
503     calf_knob_incr(widget, event->direction);
504     return TRUE;
505 }
506 
507 static void
calf_knob_class_init(CalfKnobClass * klass)508 calf_knob_class_init (CalfKnobClass *klass)
509 {
510     // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
511     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
512     widget_class->expose_event = calf_knob_expose;
513     widget_class->size_request = calf_knob_size_request;
514     widget_class->enter_notify_event = calf_knob_enter;
515     widget_class->leave_notify_event = calf_knob_leave;
516     widget_class->button_press_event = calf_knob_button_press;
517     widget_class->button_release_event = calf_knob_button_release;
518     widget_class->motion_notify_event = calf_knob_pointer_motion;
519     widget_class->key_press_event = calf_knob_key_press;
520     widget_class->key_release_event = calf_knob_key_release;
521     widget_class->scroll_event = calf_knob_scroll;
522     gtk_widget_class_install_style_property(
523         widget_class, g_param_spec_float("ring-margin", "Ring Margin", "Margin of the ring from edge",
524         0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
525     gtk_widget_class_install_style_property(
526         widget_class, g_param_spec_float("ring-width", "Ring Width", "Width of the ring",
527         0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
528     gtk_widget_class_install_style_property(
529         widget_class, g_param_spec_float("tick-margin", "Tick Margin", "Margin of the tick from edge",
530         0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
531     gtk_widget_class_install_style_property(
532         widget_class, g_param_spec_float("tick-length", "Tick Length", "Length of the tick",
533         0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
534     gtk_widget_class_install_style_property(
535         widget_class, g_param_spec_float("tick-width", "Tick Width", "Width of the tick",
536         0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE)));
537     gtk_widget_class_install_style_property(
538         widget_class, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
539         0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
540     gtk_widget_class_install_style_property(
541         widget_class, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
542         0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE)));
543 
544 }
545 
546 static void
calf_knob_init(CalfKnob * self)547 calf_knob_init (CalfKnob *self)
548 {
549     GtkWidget *widget = GTK_WIDGET(self);
550     GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
551     widget->requisition.width = 40;
552     widget->requisition.height = 40;
553     self->knob_image = NULL;
554 }
555 
556 GtkWidget *
calf_knob_new()557 calf_knob_new()
558 {
559     GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
560     return calf_knob_new_with_adjustment(adj);
561 }
562 
calf_knob_value_changed(gpointer obj)563 static gboolean calf_knob_value_changed(gpointer obj)
564 {
565     GtkWidget *widget = (GtkWidget *)obj;
566     gtk_widget_queue_draw(widget);
567     return FALSE;
568 }
569 
calf_knob_new_with_adjustment(GtkAdjustment * _adjustment)570 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
571 {
572     GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
573     if (widget) {
574         gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
575         g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
576     }
577     return widget;
578 }
579 
580 GType
calf_knob_get_type(void)581 calf_knob_get_type (void)
582 {
583     static GType type = 0;
584     if (!type) {
585 
586         static const GTypeInfo type_info = {
587             sizeof(CalfKnobClass),
588             NULL, /* base_init */
589             NULL, /* base_finalize */
590             (GClassInitFunc)calf_knob_class_init,
591             NULL, /* class_finalize */
592             NULL, /* class_data */
593             sizeof(CalfKnob),
594             0,    /* n_preallocs */
595             (GInstanceInitFunc)calf_knob_init
596         };
597 
598         for (int i = 0; ; i++) {
599             //char *name = g_strdup_printf("CalfKnob%u%d",
600                 //((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
601             const char *name = "CalfKnob";
602             if (g_type_from_name(name)) {
603                 //free(name);
604                 continue;
605             }
606             type = g_type_register_static(GTK_TYPE_RANGE,
607                                           name,
608                                           &type_info,
609                                           (GTypeFlags)0);
610             //free(name);
611             break;
612         }
613     }
614     return type;
615 }
616