3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
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.
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.
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
22 #include <calf/ctl_knob.h>
23 #include <calf/drawingutils.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #if !defined(__APPLE__)
36 #define range01(tick) std::min(1., std::max(0., tick))
38 ///////////////////////////////////////// knob ///////////////////////////////////////////////
41 calf_knob_get_color (CalfKnob
*self
, float deg
, float phase
, float start
, float last
, float tickw
, float *r
, float *g
, float *b
, float *a
)
43 GtkStateType state
= GTK_STATE_NORMAL
;
44 GtkWidget
*widget
= GTK_WIDGET(self
);
46 //printf ("get color: phase %.2f deg %.2f\n", phase, deg);
47 if (self
->type
== 0) {
49 if (!(deg
> phase
or phase
== start
))
50 state
= GTK_STATE_PRELIGHT
;
52 if (self
->type
== 1) {
54 if (deg
> 270 and deg
<= phase
and phase
> 270)
55 state
= GTK_STATE_PRELIGHT
;
56 if (deg
<= 270 and deg
> phase
and phase
< 270)
57 state
= GTK_STATE_PRELIGHT
;
58 if ((deg
== start
and phase
== start
)
59 or (deg
== 270. and phase
> 270.))
60 state
= GTK_STATE_PRELIGHT
;
62 if (self
->type
== 2) {
64 if (deg
> phase
or phase
== start
)
65 state
= GTK_STATE_PRELIGHT
;
67 if (self
->type
== 3) {
68 for (unsigned j
= 0; j
< self
->ticks
.size(); j
++) {
69 float tp
= fmod((start
+ range01(self
->ticks
[j
]) * 360.) - phase
+ 360, 360);
70 if (tp
> 360 - tickw
or tp
< tickw
) {
71 state
= GTK_STATE_PRELIGHT
;
74 if (deg
> phase
and deg
> last
+ tickw
and last
< phase
)
75 state
= GTK_STATE_PRELIGHT
;
78 get_fg_color(widget
, &state
, r
, g
, b
);
79 if (state
== GTK_STATE_NORMAL
)
80 gtk_widget_style_get(widget
, "alpha-normal", a
, NULL
);
82 gtk_widget_style_get(widget
, "alpha-prelight", a
, NULL
);
87 calf_knob_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
89 g_assert(CALF_IS_KNOB(widget
));
90 CalfKnob
*self
= CALF_KNOB(widget
);
92 if (!self
->knob_image
)
95 GdkPixbuf
*pixbuf
= self
->knob_image
;
96 gint iw
= gdk_pixbuf_get_width(pixbuf
);
97 gint ih
= gdk_pixbuf_get_height(pixbuf
);
99 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
100 cairo_t
*ctx
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
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
);
113 double ox
= widget
->allocation
.x
+ (widget
->allocation
.width
- iw
) / 2;
114 double oy
= widget
->allocation
.y
+ (widget
->allocation
.height
- ih
) / 2;
116 float rad
= size
/ 2;
117 double xc
= ox
+ rad
;
118 double yc
= oy
+ rad
;
131 double perim
= (rad
- rmargin
) * 2 * M_PI
;
132 double tickw
= 2. / perim
* 360.;
133 double tickw2
= tickw
/ 2.;
135 const unsigned int debug
= 0;
137 cairo_rectangle(ctx
, ox
, oy
, size
+ size
/ 2, size
+ size
/ 2);
141 gdk_draw_pixbuf(GDK_DRAWABLE(widget
->window
), widget
->style
->fg_gc
[0], pixbuf
,
142 0, 0, ox
, oy
, iw
, ih
, GDK_RGB_DITHER_NORMAL
, 0, 0);
144 switch (self
->type
) {
176 phase
= (adj
->value
- adj
->lower
) * base
/ (adj
->upper
- adj
->lower
) + start
;
179 state
= GTK_STATE_ACTIVE
;
180 get_fg_color(widget
, &state
, &r
, &g
, &b
);
181 float x1
= ox
+ rad
+ (rad
- tmargin
) * cos(phase
* (M_PI
/ 180.));
182 float y1
= oy
+ rad
+ (rad
- tmargin
) * sin(phase
* (M_PI
/ 180.));
183 float x2
= ox
+ rad
+ (rad
- tlength
- tmargin
) * cos(phase
* (M_PI
/ 180.));
184 float y2
= oy
+ rad
+ (rad
- tlength
- tmargin
) * sin(phase
* (M_PI
/ 180.));
185 cairo_move_to(ctx
, x1
, y1
);
186 cairo_line_to(ctx
, x2
, y2
);
187 cairo_set_source_rgba(ctx
, r
, g
, b
, 1);
188 cairo_set_line_width(ctx
, twidth
);
191 cairo_set_line_width(ctx
, rwidth
);
193 // draw ticks and rings
194 state
= GTK_STATE_NORMAL
;
195 get_fg_color(widget
, &state
, &r
, &g
, &b
);
196 unsigned int evsize
= 4;
197 double events
[4] = { start
, zero
, end
, phase
};
200 std::sort(events
, events
+ evsize
);
202 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
);
203 for (unsigned int i
= 0; i
< self
->ticks
.size(); i
++) {
204 printf("tick %d %.2f\n", i
, self
->ticks
[i
]);
208 if (debug
) printf("tick %d deg %.2f last %.2f end %.2f\n", tick
, deg
, last
, end
);
209 if (self
->ticks
.size() and deg
== start
+ range01(self
->ticks
[tick
]) * base
) {
210 // seems we want to draw a tick on this angle.
211 // so we have to fill the void between the last set angle
212 // and the point directly before the tick first.
213 // (draw from last known angle to tickw2 + tickw before actual deg)
214 if (last
< deg
- tickw
- tickw2
) {
215 calf_knob_get_color(self
, (deg
- tickw
- tickw2
), phase
, start
, last
, tickw
+ tickw2
, &r
, &g
, &b
, &opac
);
216 cairo_set_source_rgba(ctx
, r
, g
, b
, opac
);
217 cairo_arc(ctx
, xc
, yc
, rad
- rmargin
, last
* (M_PI
/ 180.), std::max(last
, std::min(nend
, (deg
- tickw
- tickw2
))) * (M_PI
/ 180.));
219 if (debug
) printf("fill from %.2f to %.2f @ %.2f\n", last
, (deg
- tickw
- tickw2
), opac
);
221 // draw the tick itself
222 calf_knob_get_color(self
, deg
, phase
, start
, end
, tickw
+ tickw2
, &r
, &g
, &b
, &opac
);
223 cairo_set_source_rgba(ctx
, r
, g
, b
, opac
);
224 cairo_arc(ctx
, xc
, yc
, rad
- rmargin
, (deg
- tickw2
) * (M_PI
/ 180.), (deg
+ tickw2
) * (M_PI
/ 180.));
226 if (debug
) printf("tick from %.2f to %.2f @ %.2f\n", (deg
- tickw2
), (deg
+ tickw2
), opac
);
227 // set last known angle to deg plus tickw + tickw2
228 last
= deg
+ tickw
+ tickw2
;
231 // remember the next ticks void end
232 if (tick
< self
->ticks
.size())
233 nend
= range01(self
->ticks
[tick
]) * base
+ start
- tickw
- tickw2
;
237 // seems we want to fill a gap between the last event and
238 // the actual one, while the actual one isn't a tick (but a
239 // knobs position or a center)
241 calf_knob_get_color(self
, deg
, phase
, start
, last
, tickw
+ tickw2
, &r
, &g
, &b
, &opac
);
242 cairo_set_source_rgba(ctx
, r
, g
, b
, opac
);
243 cairo_arc(ctx
, xc
, yc
, rad
- rmargin
, last
* (M_PI
/ 180.), std::min(nend
, std::max(last
, deg
)) * (M_PI
/ 180.));
245 if (debug
) printf("void from %.2f to %.2f @ %.2f\n", last
, std::min(nend
, std::max(last
, deg
)), opac
);
251 // set deg to next event
252 for (unsigned int i
= 0; i
< evsize
; i
++) {
253 if (debug
> 1) printf("checking %.2f (start %.2f zero %.2f phase %.2f end %.2f)\n", events
[i
], start
, zero
, phase
, end
);
254 if (events
[i
] > deg
) {
256 if (debug
> 1) printf("taken.\n");
260 if (tick
< self
->ticks
.size()) {
261 deg
= std::min(deg
, start
+ range01(self
->ticks
[tick
]) * base
);
262 if (debug
> 1) printf("checking tick %d %.2f\n", tick
, start
+ range01(self
->ticks
[tick
]) * base
);
264 //deg = std::max(last, deg);
265 if (debug
> 1) printf("finally! deg %.2f\n", deg
);
267 if (debug
) printf("\n");
273 calf_knob_size_request (GtkWidget
*widget
,
274 GtkRequisition
*requisition
)
276 g_assert(CALF_IS_KNOB(widget
));
277 CalfKnob
*self
= CALF_KNOB(widget
);
278 if (!self
->knob_image
)
280 requisition
->width
= gdk_pixbuf_get_width(self
->knob_image
);
281 requisition
->height
= gdk_pixbuf_get_height(self
->knob_image
);
285 calf_knob_set_size (CalfKnob
*self
, int size
)
288 GtkWidget
*widget
= GTK_WIDGET(self
);
290 sprintf(name
, "%s_%d\n", gtk_widget_get_name(widget
), size
);
291 gtk_widget_set_name(widget
, name
);
292 gtk_widget_queue_resize(widget
);
296 calf_knob_set_pixbuf (CalfKnob
*self
, GdkPixbuf
*pixbuf
)
298 self
->knob_image
= pixbuf
;
299 gtk_widget_queue_resize(GTK_WIDGET(self
));
302 static gboolean
calf_knob_enter (GtkWidget
*widget
, GdkEventCrossing
* ev
)
304 if (gtk_widget_get_state(widget
) == GTK_STATE_NORMAL
) {
305 gtk_widget_set_state(widget
, GTK_STATE_PRELIGHT
);
306 gtk_widget_queue_draw(widget
);
311 static gboolean
calf_knob_leave (GtkWidget
*widget
, GdkEventCrossing
*ev
)
313 if (gtk_widget_get_state(widget
) == GTK_STATE_PRELIGHT
) {
314 gtk_widget_set_state(widget
, GTK_STATE_NORMAL
);
315 gtk_widget_queue_draw(widget
);
321 calf_knob_incr (GtkWidget
*widget
, int dir_down
)
323 g_assert(CALF_IS_KNOB(widget
));
324 CalfKnob
*self
= CALF_KNOB(widget
);
325 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
327 int oldstep
= (int)(0.5f
+ (adj
->value
- adj
->lower
) / adj
->step_increment
);
329 int nsteps
= (int)(0.5f
+ (adj
->upper
- adj
->lower
) / adj
->step_increment
); // less 1 actually
334 if (self
->type
== 3 && step
>= nsteps
)
336 if (self
->type
== 3 && step
< 0)
337 step
= nsteps
- (nsteps
- step
) % nsteps
;
339 // trying to reduce error cumulation here, by counting from lowest or from highest
340 float value
= adj
->lower
+ step
* double(adj
->upper
- adj
->lower
) / nsteps
;
341 gtk_range_set_value(GTK_RANGE(widget
), value
);
342 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
346 calf_knob_key_press (GtkWidget
*widget
, GdkEventKey
*event
)
348 g_assert(CALF_IS_KNOB(widget
));
349 CalfKnob
*self
= CALF_KNOB(widget
);
350 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
351 gtk_widget_set_state(widget
, GTK_STATE_ACTIVE
);
352 gtk_widget_queue_draw(widget
);
353 switch(event
->keyval
)
356 gtk_range_set_value(GTK_RANGE(widget
), adj
->lower
);
360 gtk_range_set_value(GTK_RANGE(widget
), adj
->upper
);
364 calf_knob_incr(widget
, 0);
368 calf_knob_incr(widget
, 1);
373 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
374 self
->start_y
= self
->last_y
;
382 calf_knob_key_release (GtkWidget
*widget
, GdkEventKey
*event
)
384 g_assert(CALF_IS_KNOB(widget
));
385 CalfKnob
*self
= CALF_KNOB(widget
);
387 if(event
->keyval
== GDK_Shift_L
|| event
->keyval
== GDK_Shift_R
)
389 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
390 self
->start_y
= self
->last_y
;
393 gtk_widget_set_state(widget
, GTK_STATE_NORMAL
);
394 gtk_widget_queue_draw(widget
);
399 calf_knob_button_press (GtkWidget
*widget
, GdkEventButton
*event
)
401 g_assert(CALF_IS_KNOB(widget
));
402 CalfKnob
*self
= CALF_KNOB(widget
);
404 if (event
->type
== GDK_2BUTTON_PRESS
) {
405 gtk_range_set_value(GTK_RANGE(widget
), self
->default_value
);
408 // CalfKnob *lg = CALF_KNOB(widget);
409 gtk_widget_grab_focus(widget
);
410 gtk_grab_add(widget
);
411 self
->start_x
= event
->x
;
412 self
->last_y
= self
->start_y
= event
->y
;
413 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
414 gtk_widget_set_state(widget
, GTK_STATE_ACTIVE
);
415 gtk_widget_queue_draw(widget
);
420 calf_knob_button_release (GtkWidget
*widget
, GdkEventButton
*event
)
422 g_assert(CALF_IS_KNOB(widget
));
424 if (GTK_WIDGET_HAS_GRAB(widget
))
425 gtk_grab_remove(widget
);
426 gtk_widget_set_state(widget
, GTK_STATE_NORMAL
);
427 gtk_widget_queue_draw(widget
);
431 static inline float endless(float value
)
434 return fmod(value
, 1.f
);
436 return fmod(1.f
- fmod(1.f
- value
, 1.f
), 1.f
);
439 static inline float deadzone(GtkWidget
*widget
, float value
, float incr
)
448 float nv
= ov
+ incr
;
458 calf_knob_pointer_motion (GtkWidget
*widget
, GdkEventMotion
*event
)
460 g_assert(CALF_IS_KNOB(widget
));
461 CalfKnob
*self
= CALF_KNOB(widget
);
463 float scale
= (event
->state
& GDK_SHIFT_MASK
) ? 2500 : 250;
464 gboolean moved
= FALSE
;
466 if (GTK_WIDGET_HAS_GRAB(widget
))
470 gtk_range_set_value(GTK_RANGE(widget
), endless(self
->start_value
- (event
->y
- self
->start_y
) / scale
));
475 gtk_range_set_value(GTK_RANGE(widget
), deadzone(GTK_WIDGET(widget
), self
->start_value
, -(event
->y
- self
->start_y
) / scale
));
479 gtk_range_set_value(GTK_RANGE(widget
), self
->start_value
- (event
->y
- self
->start_y
) / scale
);
483 self
->last_y
= event
->y
;
488 calf_knob_scroll (GtkWidget
*widget
, GdkEventScroll
*event
)
490 calf_knob_incr(widget
, event
->direction
);
495 calf_knob_class_init (CalfKnobClass
*klass
)
497 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
498 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
499 widget_class
->expose_event
= calf_knob_expose
;
500 widget_class
->size_request
= calf_knob_size_request
;
501 widget_class
->enter_notify_event
= calf_knob_enter
;
502 widget_class
->leave_notify_event
= calf_knob_leave
;
503 widget_class
->button_press_event
= calf_knob_button_press
;
504 widget_class
->button_release_event
= calf_knob_button_release
;
505 widget_class
->motion_notify_event
= calf_knob_pointer_motion
;
506 widget_class
->key_press_event
= calf_knob_key_press
;
507 widget_class
->key_release_event
= calf_knob_key_release
;
508 widget_class
->scroll_event
= calf_knob_scroll
;
509 gtk_widget_class_install_style_property(
510 widget_class
, g_param_spec_float("ring-margin", "Ring Margin", "Margin of the ring from edge",
511 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE
)));
512 gtk_widget_class_install_style_property(
513 widget_class
, g_param_spec_float("ring-width", "Ring Width", "Width of the ring",
514 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE
)));
515 gtk_widget_class_install_style_property(
516 widget_class
, g_param_spec_float("tick-margin", "Tick Margin", "Margin of the tick from edge",
517 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE
)));
518 gtk_widget_class_install_style_property(
519 widget_class
, g_param_spec_float("tick-length", "Tick Length", "Length of the tick",
520 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE
)));
521 gtk_widget_class_install_style_property(
522 widget_class
, g_param_spec_float("tick-width", "Tick Width", "Width of the tick",
523 0.0, 100.0, 0.0, GParamFlags(G_PARAM_READWRITE
)));
524 gtk_widget_class_install_style_property(
525 widget_class
, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
526 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE
)));
527 gtk_widget_class_install_style_property(
528 widget_class
, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
529 0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE
)));
534 calf_knob_init (CalfKnob
*self
)
536 GtkWidget
*widget
= GTK_WIDGET(self
);
537 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self
), GTK_CAN_FOCUS
);
538 widget
->requisition
.width
= 40;
539 widget
->requisition
.height
= 40;
540 self
->knob_image
= NULL
;
546 GtkAdjustment
*adj
= (GtkAdjustment
*)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
547 return calf_knob_new_with_adjustment(adj
);
550 static gboolean
calf_knob_value_changed(gpointer obj
)
552 GtkWidget
*widget
= (GtkWidget
*)obj
;
553 gtk_widget_queue_draw(widget
);
557 GtkWidget
*calf_knob_new_with_adjustment(GtkAdjustment
*_adjustment
)
559 GtkWidget
*widget
= GTK_WIDGET( g_object_new (CALF_TYPE_KNOB
, NULL
));
561 gtk_range_set_adjustment(GTK_RANGE(widget
), _adjustment
);
562 g_signal_connect(GTK_OBJECT(widget
), "value-changed", G_CALLBACK(calf_knob_value_changed
), widget
);
568 calf_knob_get_type (void)
570 static GType type
= 0;
573 static const GTypeInfo type_info
= {
574 sizeof(CalfKnobClass
),
575 NULL
, /* base_init */
576 NULL
, /* base_finalize */
577 (GClassInitFunc
)calf_knob_class_init
,
578 NULL
, /* class_finalize */
579 NULL
, /* class_data */
582 (GInstanceInitFunc
)calf_knob_init
585 for (int i
= 0; ; i
++) {
586 char *name
= g_strdup_printf("CalfKnob%u%d",
587 ((unsigned int)(intptr_t)calf_knob_class_init
) >> 16, i
);
588 //const char *name = "CalfKnob";
589 if (g_type_from_name(name
)) {
593 type
= g_type_register_static(GTK_TYPE_RANGE
,