Make Analyzer UI require instance-access
[calf.git] / src / custom_ctl.cpp
blob3dc8a8846be3fd0e5d4c2bb5dc078a3cd9cbbe39
1 /* Calf DSP Library
2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007-2010 Krzysztof Foltman, Torben Hohn, Markus Schmidt
4 * and others
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
21 #include <gtk/gtkrange.h>
22 #include "config.h"
23 #include <calf/giface.h>
24 #include <calf/custom_ctl.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <sys/stat.h>
27 #include <math.h>
28 #include <gdk/gdk.h>
29 #include <gtk/gtk.h>
30 #include <sys/time.h>
31 #include <algorithm>
32 #include <iostream>
33 #include <calf/drawingutils.h>
35 using namespace calf_plugins;
36 using namespace dsp;
39 ///////////////////////////////////////// meter scale ///////////////////////////////////////////////
41 GtkWidget *
42 calf_meter_scale_new()
44 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_METER_SCALE, NULL ));
45 //CalfMeterScale *self = CALF_METER_SCALE(widget);
46 return widget;
48 static gboolean
49 calf_meter_scale_expose (GtkWidget *widget, GdkEventExpose *event)
51 g_assert(CALF_IS_METER_SCALE(widget));
52 CalfMeterScale *ms = CALF_METER_SCALE(widget);
53 if (gtk_widget_is_drawable (widget)) {
55 GdkWindow *window = widget->window;
56 cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(window));
57 cairo_text_extents_t extents;
59 double ox = widget->allocation.x;
60 double oy = widget->allocation.y;
61 double sx = widget->allocation.width;
62 double sy = widget->allocation.height;
63 double width = widget->allocation.width;
64 double height = widget->allocation.height;
65 double xthick = widget->style->xthickness;
66 double text_w = 0, bar_x = 0, bar_width = 0, bar_y = 0;
67 float r, g, b;
68 double text_m = 3;
69 double dot_s = 2;
70 double dot_m = 2;
71 double dot_y = 0;
72 double dot_y2 = 0;
73 cairo_rectangle(cr, ox, oy, sx, sy);
74 cairo_clip(cr);
76 if (ms->position) {
77 cairo_select_font_face(cr, "cairo:sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
78 cairo_set_font_size(cr, 8);
79 cairo_text_extents(cr, "-88.88", &extents);
80 text_w = extents.width;
82 switch(ms->position) {
83 case 1:
84 // label on top
85 bar_x = ox + xthick;
86 bar_width = width - 2 * xthick;
87 break;
88 case 2:
89 // label right
90 bar_x = ox + xthick;
91 bar_width = width - text_w - 2 * xthick - 2 * text_m;
92 break;
93 case 3:
94 // label bottom
95 bar_x = ox + xthick;
96 bar_width = width - 2 * xthick;
97 break;
98 case 4:
99 // label left
100 bar_x = ox + xthick + text_w + 2 * text_m;
101 bar_width = width - text_w - 2 * xthick - 2 * text_m;
102 break;
105 switch (ms->dots) {
106 case 0:
107 default:
108 // no ticks
109 bar_y = height / 2;
110 break;
111 case 1:
112 // tick top
113 bar_y = oy + dot_s + dot_m + extents.height;
114 dot_y = oy + dot_s / 2;
115 break;
116 case 2:
117 // ticks bottom
118 bar_y = oy + height - dot_s - dot_m - extents.height + extents.y_bearing;
119 dot_y = oy + height - dot_s / 2;
120 break;
121 case 3:
122 // ticks center
123 bar_y = oy + height / 2 - extents.y_bearing / 2;
124 dot_y = oy + height - dot_s / 2;
125 dot_y2 = oy + dot_s / 2;
126 break;
129 const unsigned int s = ms->marker.size();
130 get_fg_color(widget, NULL, &r, &g, &b);
131 cairo_set_source_rgb(cr, r, g, b);
132 for (unsigned int i = 0; i < s; i++) {
133 double val = log10(1 + ms->marker[i] * 9);
134 if (ms->dots) {
135 cairo_arc(cr, bar_x + bar_width * val, dot_y, dot_s / 2, 0, 2*M_PI);
136 cairo_fill(cr);
138 if (ms->dots == 3) {
139 cairo_arc(cr, bar_x + bar_width * val, dot_y2, dot_s / 2, 0, 2*M_PI);
140 cairo_fill(cr);
142 char str[32];
143 if (val < 1.0 / 32768.0)
144 snprintf(str, sizeof(str), "-inf");
145 else
146 snprintf(str, sizeof(str), "%.f", amp2dB(ms->marker[i]));
147 cairo_text_extents(cr, str, &extents);
148 cairo_move_to(cr,
149 std::min(ox + width, std::max(ox, bar_x + bar_width * val - extents.width / 2)), bar_y);
150 cairo_show_text(cr, str);
152 cairo_destroy(cr);
154 return FALSE;
157 static void
158 calf_meter_scale_size_request (GtkWidget *widget,
159 GtkRequisition *requisition)
161 g_assert(CALF_IS_METER_SCALE(widget));
162 CalfMeterScale *self = CALF_METER_SCALE(widget);
164 double ythick = widget->style->ythickness;
165 double text_h = 8; // FIXME: Pango layout should be used here
166 double dot_s = 2;
167 double dot_m = 2;
169 requisition->height = ythick*2 + text_h + (dot_m + dot_s) * (self->dots == 3 ? 2 : 1);
172 static void
173 calf_meter_scale_class_init (CalfMeterScaleClass *klass)
175 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
176 widget_class->expose_event = calf_meter_scale_expose;
177 widget_class->size_request = calf_meter_scale_size_request;
180 static void
181 calf_meter_scale_init (CalfMeterScale *self)
183 GtkWidget *widget = GTK_WIDGET(self);
184 gtk_widget_set_has_window(widget, FALSE);
185 widget->requisition.width = 40;
186 widget->requisition.height = 12;
187 self->mode = VU_STANDARD;
188 self->position = 0;
189 self->dots = 0;
192 GType
193 calf_meter_scale_get_type (void)
195 static GType type = 0;
196 if (!type) {
197 static const GTypeInfo type_info = {
198 sizeof(CalfMeterScaleClass),
199 NULL, /* base_init */
200 NULL, /* base_finalize */
201 (GClassInitFunc)calf_meter_scale_class_init,
202 NULL, /* class_finalize */
203 NULL, /* class_data */
204 sizeof(CalfMeterScale),
205 0, /* n_preallocs */
206 (GInstanceInitFunc)calf_meter_scale_init
209 for (int i = 0; ; i++) {
210 char *name = g_strdup_printf("CalfMeterScale%u%d",
211 ((unsigned int)(intptr_t)calf_meter_scale_class_init) >> 16, i);
212 if (g_type_from_name(name)) {
213 free(name);
214 continue;
216 type = g_type_register_static(GTK_TYPE_DRAWING_AREA,
217 name,
218 &type_info,
219 (GTypeFlags)0);
220 free(name);
221 break;
224 return type;
228 ///////////////////////////////////////// phase graph ///////////////////////////////////////////////
230 static void
231 calf_phase_graph_draw_background(GtkWidget *widget, cairo_t *ctx, int sx, int sy, int ox, int oy, float radius, float bevel )
233 int cx = ox + sx / 2;
234 int cy = oy + sy / 2;
236 display_background(widget, ctx, 0, 0, sx, sy, ox, oy, radius, bevel);
237 cairo_set_source_rgb(ctx, 0.35, 0.4, 0.2);
238 cairo_select_font_face(ctx, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
239 cairo_set_font_size(ctx, 9);
240 cairo_text_extents_t te;
242 cairo_text_extents (ctx, "M", &te);
243 cairo_move_to (ctx, cx + 5, oy + 12);
244 cairo_show_text (ctx, "M");
246 cairo_text_extents (ctx, "S", &te);
247 cairo_move_to (ctx, ox + 5, cy - 5);
248 cairo_show_text (ctx, "S");
250 cairo_text_extents (ctx, "L", &te);
251 cairo_move_to (ctx, ox + 18, oy + 12);
252 cairo_show_text (ctx, "L");
254 cairo_text_extents (ctx, "R", &te);
255 cairo_move_to (ctx, ox + sx - 22, oy + 12);
256 cairo_show_text (ctx, "R");
258 cairo_set_line_width(ctx, 1);
260 cairo_move_to(ctx, ox, oy + sy * 0.5);
261 cairo_line_to(ctx, ox + sx, oy + sy * 0.5);
262 cairo_stroke(ctx);
264 cairo_move_to(ctx, ox + sx * 0.5, oy);
265 cairo_line_to(ctx, ox + sx * 0.5, oy + sy);
266 cairo_stroke(ctx);
268 cairo_set_source_rgba(ctx, 0, 0, 0, 0.2);
269 cairo_move_to(ctx, ox, oy);
270 cairo_line_to(ctx, ox + sx, oy + sy);
271 cairo_stroke(ctx);
273 cairo_move_to(ctx, ox, oy + sy);
274 cairo_line_to(ctx, ox + sx, oy);
275 cairo_stroke(ctx);
277 static void
278 calf_phase_graph_copy_surface(cairo_t *ctx, cairo_surface_t *source, int x = 0, int y = 0, float fade = 1.f)
280 // copy a surface to a cairo context
281 cairo_save(ctx);
282 cairo_set_source_surface(ctx, source, x, y);
283 if (fade < 1.0) {
284 float rnd = (float)rand() / (float)RAND_MAX / 100;
285 cairo_paint_with_alpha(ctx, fade * 0.35 + 0.05 + rnd);
286 } else {
287 cairo_paint(ctx);
289 cairo_restore(ctx);
291 static gboolean
292 calf_phase_graph_expose (GtkWidget *widget, GdkEventExpose *event)
294 g_assert(CALF_IS_PHASE_GRAPH(widget));
295 CalfPhaseGraph *pg = CALF_PHASE_GRAPH(widget);
296 if (!pg->source)
297 return FALSE;
299 // dimensions
300 int width = widget->allocation.width;
301 int height = widget->allocation.height;
302 int ox = widget->style->xthickness;
303 int oy = widget->style->ythickness;
304 int sx = width - ox * 2;
305 int sy = height - oy * 2;
306 int x = widget->allocation.x;
307 int y = widget->allocation.y;
309 sx += sx % 2 - 1;
310 sy += sy % 2 - 1;
311 int rad = sx / 2;
312 int cx = ox + sx / 2;
313 int cy = oy + sy / 2;
315 float radius, bevel;
316 gtk_widget_style_get(widget, "border-radius", &radius, "bevel", &bevel, NULL);
318 // some values as pointers for the audio plug-in call
319 float * phase_buffer = 0;
320 int length = 0;
321 int mode = 2;
322 float fade = 0.05;
323 bool use_fade = true;
324 int accuracy = 1;
325 bool display = true;
327 // cairo initialization stuff
328 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
329 cairo_t *ctx_back;
330 cairo_t *ctx_cache;
332 if( pg->background == NULL ) {
333 // looks like its either first call or the widget has been resized.
334 // create the background surface (stolen from line graph)...
335 pg->background = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height );
336 pg->cache = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height );
338 // ...and draw some bling bling onto it...
339 ctx_back = cairo_create(pg->background);
340 calf_phase_graph_draw_background(widget, ctx_back, sx, sy, ox, oy, radius, bevel);
341 // ...and copy it to the cache
342 ctx_cache = cairo_create(pg->cache);
343 calf_phase_graph_copy_surface(ctx_cache, pg->background, 0, 0, 1);
344 } else {
345 ctx_back = cairo_create(pg->background);
346 ctx_cache = cairo_create(pg->cache);
349 pg->source->get_phase_graph(&phase_buffer, &length, &mode, &use_fade,
350 &fade, &accuracy, &display);
352 // process some values set by the plug-in
353 accuracy *= 2;
354 accuracy = 12 - accuracy;
356 cairo_rectangle(ctx_cache, ox, oy, sx, sy);
357 cairo_clip(ctx_cache);
359 calf_phase_graph_copy_surface(ctx_cache, pg->background, 0, 0, use_fade ? fade : 1);
361 if(display) {
362 cairo_rectangle(ctx_cache, ox, oy, sx, sy);
363 cairo_clip(ctx_cache);
364 cairo_set_source_rgba(ctx_cache, 0.35, 0.4, 0.2, 1);
365 double _a;
366 for(int i = 0; i < length; i+= accuracy) {
367 float l = phase_buffer[i];
368 float r = phase_buffer[i + 1];
369 if(l == 0.f and r == 0.f) continue;
370 else if(r == 0.f and l > 0.f) _a = M_PI / 2.f;
371 else if(r == 0.f and l < 0.f) _a = 3.f *M_PI / 2.f;
372 else _a = pg->_atan(l / r, l, r);
373 double _R = sqrt(pow(l, 2) + pow(r, 2));
374 _a += M_PI / 4.f;
375 float x_ = (-1.f)*_R * cos(_a);
376 float y_ = _R * sin(_a);
377 // mask the cached values
378 switch(mode) {
379 case 0:
380 // small dots
381 cairo_rectangle (ctx_cache, x_ * rad + cx, y_ * rad + cy, 1, 1);
382 break;
383 case 1:
384 // medium dots
385 cairo_rectangle (ctx_cache, x_ * rad + cx - 0.25, y_ * rad + cy - 0.25, 1.5, 1.5);
386 break;
387 case 2:
388 // big dots
389 cairo_rectangle (ctx_cache, x_ * rad + cx - 0.5, y_ * rad + cy - 0.5, 2, 2);
390 break;
391 case 3:
392 // fields
393 if(i == 0) cairo_move_to(ctx_cache,
394 x_ * rad + cx, y_ * rad + cy);
395 else cairo_line_to(ctx_cache,
396 x_ * rad + cx, y_ * rad + cy);
397 break;
398 case 4:
399 // lines
400 if(i == 0) cairo_move_to(ctx_cache,
401 x_ * rad + cx, y_ * rad + cy);
402 else cairo_line_to(ctx_cache,
403 x_ * rad + cx, y_ * rad + cy);
404 break;
407 // draw
408 switch(mode) {
409 case 0:
410 case 1:
411 case 2:
412 cairo_fill(ctx_cache);
413 break;
414 case 3:
415 cairo_fill(ctx_cache);
416 break;
417 case 4:
418 cairo_set_line_width(ctx_cache, 0.5);
419 cairo_stroke(ctx_cache);
420 break;
424 calf_phase_graph_copy_surface(c, pg->cache, x, y);
426 cairo_destroy(c);
427 cairo_destroy(ctx_back);
428 cairo_destroy(ctx_cache);
429 // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
430 return TRUE;
433 static void
434 calf_phase_graph_size_request (GtkWidget *widget,
435 GtkRequisition *requisition)
437 g_assert(CALF_IS_PHASE_GRAPH(widget));
438 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
441 static void
442 calf_phase_graph_size_allocate (GtkWidget *widget,
443 GtkAllocation *allocation)
445 g_assert(CALF_IS_PHASE_GRAPH(widget));
446 CalfPhaseGraph *lg = CALF_PHASE_GRAPH(widget);
448 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_PHASE_GRAPH_GET_CLASS( lg ) );
450 if(lg->background)
451 cairo_surface_destroy(lg->background);
452 lg->background = NULL;
454 widget->allocation = *allocation;
455 GtkAllocation &a = widget->allocation;
456 if (a.width > a.height) {
457 a.x += (a.width - a.height) / 2;
458 a.width = a.height;
460 if (a.width < a.height) {
461 a.y += (a.height - a.width) / 2;
462 a.height = a.width;
464 parent_class->size_allocate(widget, &a);
467 static void
468 calf_phase_graph_class_init (CalfPhaseGraphClass *klass)
470 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
471 widget_class->expose_event = calf_phase_graph_expose;
472 widget_class->size_request = calf_phase_graph_size_request;
473 widget_class->size_allocate = calf_phase_graph_size_allocate;
474 gtk_widget_class_install_style_property(
475 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
476 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
477 gtk_widget_class_install_style_property(
478 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
479 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
482 static void
483 calf_phase_graph_unrealize (GtkWidget *widget, CalfPhaseGraph *pg)
485 if( pg->background )
486 cairo_surface_destroy(pg->background);
487 pg->background = NULL;
490 static void
491 calf_phase_graph_init (CalfPhaseGraph *self)
493 GtkWidget *widget = GTK_WIDGET(self);
494 widget->requisition.width = 40;
495 widget->requisition.height = 40;
496 self->background = NULL;
497 gtk_widget_set_has_window(widget, FALSE);
498 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_phase_graph_unrealize), (gpointer)self);
501 GtkWidget *
502 calf_phase_graph_new()
504 return GTK_WIDGET(g_object_new (CALF_TYPE_PHASE_GRAPH, NULL));
507 GType
508 calf_phase_graph_get_type (void)
510 static GType type = 0;
511 if (!type) {
512 static const GTypeInfo type_info = {
513 sizeof(CalfPhaseGraphClass),
514 NULL, /* base_init */
515 NULL, /* base_finalize */
516 (GClassInitFunc)calf_phase_graph_class_init,
517 NULL, /* class_finalize */
518 NULL, /* class_data */
519 sizeof(CalfPhaseGraph),
520 0, /* n_preallocs */
521 (GInstanceInitFunc)calf_phase_graph_init
524 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
526 for (int i = 0; ; i++) {
527 //const char *name = "CalfPhaseGraph";
528 char *name = g_strdup_printf("CalfPhaseGraph%u%d", ((unsigned int)(intptr_t)calf_phase_graph_class_init) >> 16, i);
529 if (g_type_from_name(name)) {
530 free(name);
531 continue;
533 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
534 name,
535 type_info_copy,
536 (GTypeFlags)0);
537 free(name);
538 break;
541 return type;
545 ///////////////////////////////////////// toggle ///////////////////////////////////////////////
547 static gboolean
548 calf_toggle_expose (GtkWidget *widget, GdkEventExpose *event)
550 g_assert(CALF_IS_TOGGLE(widget));
551 CalfToggle *self = CALF_TOGGLE(widget);
552 if (!self->toggle_image)
553 return FALSE;
554 float off = floor(.5 + gtk_range_get_value(GTK_RANGE(widget)));
555 float pw = gdk_pixbuf_get_width(self->toggle_image);
556 float ph = gdk_pixbuf_get_height(self->toggle_image);
557 float wcx = widget->allocation.x + widget->allocation.width / 2;
558 float wcy = widget->allocation.y + widget->allocation.height / 2;
559 float pcx = pw / 2;
560 float pcy = ph / 4;
561 float sy = off * ph / 2;
562 float x = wcx - pcx;
563 float y = wcy - pcy;
564 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0],
565 self->toggle_image, 0, sy, x, y, pw, ph / 2, GDK_RGB_DITHER_NORMAL, 0, 0);
566 return TRUE;
569 static void
570 calf_toggle_size_request (GtkWidget *widget,
571 GtkRequisition *requisition)
573 g_assert(CALF_IS_TOGGLE(widget));
574 requisition->width = widget->style->xthickness;
575 requisition->height = widget->style->ythickness;
578 static gboolean
579 calf_toggle_button_press (GtkWidget *widget, GdkEventButton *event)
581 g_assert(CALF_IS_TOGGLE(widget));
582 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
583 if (gtk_range_get_value(GTK_RANGE(widget)) == adj->lower)
585 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
586 } else {
587 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
589 return TRUE;
592 static gboolean
593 calf_toggle_key_press (GtkWidget *widget, GdkEventKey *event)
595 switch(event->keyval)
597 case GDK_Return:
598 case GDK_KP_Enter:
599 case GDK_space:
600 return calf_toggle_button_press(widget, NULL);
602 return FALSE;
605 static void
606 calf_toggle_class_init (CalfToggleClass *klass)
608 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
609 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
610 widget_class->expose_event = calf_toggle_expose;
611 widget_class->size_request = calf_toggle_size_request;
612 widget_class->button_press_event = calf_toggle_button_press;
613 widget_class->key_press_event = calf_toggle_key_press;
616 static void
617 calf_toggle_init (CalfToggle *self)
619 GtkWidget *widget = GTK_WIDGET(self);
620 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
621 widget->requisition.width = 30;
622 widget->requisition.height = 20;
623 self->size = 1;
626 void
627 calf_toggle_set_size (CalfToggle *self, int size)
629 char name[128];
630 GtkWidget *widget = GTK_WIDGET(self);
631 self->size = size;
632 sprintf(name, "%s_%d\n", gtk_widget_get_name(widget), size);
633 gtk_widget_set_name(widget, name);
634 gtk_widget_queue_resize(widget);
636 void
637 calf_toggle_set_pixbuf (CalfToggle *self, GdkPixbuf *pixbuf)
639 GtkWidget *widget = GTK_WIDGET(self);
640 self->toggle_image = pixbuf;
641 gtk_widget_queue_resize(widget);
644 GtkWidget *
645 calf_toggle_new()
647 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 1, 0, 0);
648 return calf_toggle_new_with_adjustment(adj);
651 static gboolean calf_toggle_value_changed(gpointer obj)
653 GtkWidget *widget = (GtkWidget *)obj;
654 CalfToggle *self = CALF_TOGGLE(widget);
655 float sx = self->size ? self->size : 1.f / 3.f * 2.f;
656 float sy = self->size ? self->size : 1;
657 gtk_widget_queue_draw_area(widget,
658 widget->allocation.x - sx * 2,
659 widget->allocation.y - sy * 3,
660 self->size * 34,
661 self->size * 26);
662 return FALSE;
665 GtkWidget *calf_toggle_new_with_adjustment(GtkAdjustment *_adjustment)
667 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TOGGLE, NULL ));
668 if (widget) {
669 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
670 g_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_toggle_value_changed), widget);
672 return widget;
675 GType
676 calf_toggle_get_type (void)
678 static GType type = 0;
679 if (!type) {
681 static const GTypeInfo type_info = {
682 sizeof(CalfToggleClass),
683 NULL, /* base_init */
684 NULL, /* base_finalize */
685 (GClassInitFunc)calf_toggle_class_init,
686 NULL, /* class_finalize */
687 NULL, /* class_data */
688 sizeof(CalfToggle),
689 0, /* n_preallocs */
690 (GInstanceInitFunc)calf_toggle_init
693 for (int i = 0; ; i++) {
694 char *name = g_strdup_printf("CalfToggle%u%d",
695 ((unsigned int)(intptr_t)calf_toggle_class_init) >> 16, i);
696 if (g_type_from_name(name)) {
697 free(name);
698 continue;
700 type = g_type_register_static( GTK_TYPE_RANGE,
701 name,
702 &type_info,
703 (GTypeFlags)0);
704 free(name);
705 break;
708 return type;
711 ///////////////////////////////////////// frame ///////////////////////////////////////////////
714 GtkWidget *
715 calf_frame_new(const char *label)
717 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_FRAME, NULL ));
718 CalfFrame *self = CALF_FRAME(widget);
719 gtk_frame_set_label(GTK_FRAME(self), label);
720 return widget;
722 static gboolean
723 calf_frame_expose (GtkWidget *widget, GdkEventExpose *event)
725 g_assert(CALF_IS_FRAME(widget));
726 if (gtk_widget_is_drawable (widget)) {
728 GdkWindow *window = widget->window;
729 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
730 cairo_text_extents_t extents;
732 int ox = widget->allocation.x;
733 int oy = widget->allocation.y;
734 int sx = widget->allocation.width;
735 int sy = widget->allocation.height;
737 float rad;
738 gtk_widget_style_get(widget, "border-radius", &rad, NULL);
740 double pad = widget->style->xthickness;
741 double txp = 4;
742 double m = 0.5;
743 double size = 10;
745 float r, g, b;
747 cairo_rectangle(c, ox, oy, sx, sy);
748 cairo_clip(c);
751 const gchar *lab = gtk_frame_get_label(GTK_FRAME(widget));
753 cairo_select_font_face(c, "Sans",
754 CAIRO_FONT_SLANT_NORMAL,
755 CAIRO_FONT_WEIGHT_NORMAL);
756 cairo_set_font_size(c, size);
758 cairo_text_extents(c, lab, &extents);
760 double lw = extents.width + txp * 2.;
762 cairo_set_line_width(c, 1.);
764 cairo_move_to(c, ox + rad + txp + m, oy + size - 2 + m);
765 get_text_color(widget, NULL, &r, &g, &b);
766 cairo_set_source_rgb(c, r, g, b);
767 cairo_show_text(c, lab);
768 get_fg_color(widget, NULL, &r, &g, &b);
769 cairo_set_source_rgb(c, r, g, b);
771 // top left
772 cairo_move_to(c, ox + m, oy + pad + rad + m);
773 cairo_arc (c, ox + rad + m, oy + rad + pad + m, rad, 1 * M_PI, 1.5 * M_PI);
774 // top
775 cairo_move_to(c, ox + rad + lw + m, oy + pad + m);
776 cairo_line_to(c, ox + sx - rad - m, oy + pad + m);
777 // top right
778 cairo_arc (c, ox + sx - rad - m, oy + rad + pad + m, rad, 1.5 * M_PI, 2 * M_PI);
779 // right
780 cairo_line_to(c, ox + sx - m, oy + sy - rad - m);
781 // bottom right
782 cairo_arc (c, ox + sx - rad - m, oy + sy - rad - m, rad, 0 * M_PI, 0.5 * M_PI);
783 // bottom
784 cairo_line_to(c, ox + rad + m, oy + sy - m);
785 // bottom left
786 cairo_arc (c, ox + rad + m, oy + sy - rad - m, rad, 0.5 * M_PI, 1 * M_PI);
787 // left
788 cairo_line_to(c, ox + m, oy + rad + pad + m);
789 cairo_stroke(c);
791 cairo_destroy(c);
793 if (gtk_bin_get_child(GTK_BIN(widget))) {
794 gtk_container_propagate_expose(GTK_CONTAINER(widget),
795 gtk_bin_get_child(GTK_BIN(widget)),
796 event);
798 return FALSE;
801 static void
802 calf_frame_class_init (CalfFrameClass *klass)
804 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
805 widget_class->expose_event = calf_frame_expose;
806 gtk_widget_class_install_style_property(
807 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
808 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
811 static void
812 calf_frame_init (CalfFrame *self)
814 GtkWidget *widget = GTK_WIDGET(self);
815 widget->requisition.width = 40;
816 widget->requisition.height = 40;
819 GType
820 calf_frame_get_type (void)
822 static GType type = 0;
823 if (!type) {
824 static const GTypeInfo type_info = {
825 sizeof(CalfFrameClass),
826 NULL, /* base_init */
827 NULL, /* base_finalize */
828 (GClassInitFunc)calf_frame_class_init,
829 NULL, /* class_finalize */
830 NULL, /* class_data */
831 sizeof(CalfFrame),
832 0, /* n_preallocs */
833 (GInstanceInitFunc)calf_frame_init
836 for (int i = 0; ; i++) {
837 //const char *name = "CalfFrame";
838 char *name = g_strdup_printf("CalfFrame%u%d",
839 ((unsigned int)(intptr_t)calf_frame_class_init) >> 16, i);
840 if (g_type_from_name(name)) {
841 free(name);
842 continue;
844 type = g_type_register_static(GTK_TYPE_FRAME,
845 name,
846 &type_info,
847 (GTypeFlags)0);
848 free(name);
849 break;
852 return type;
855 ///////////////////////////////////////// combo box ///////////////////////////////////////////////
857 //#define GTK_COMBO_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_COMBO_BOX, GtkComboBoxPrivate))
859 GtkWidget *
860 calf_combobox_new()
862 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_COMBOBOX, NULL ));
863 GtkCellRenderer *column = gtk_cell_renderer_text_new();
864 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), column, TRUE);
865 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(widget), column,
866 "text", 0,
867 NULL);
868 return widget;
870 static gboolean
871 calf_combobox_expose (GtkWidget *widget, GdkEventExpose *event)
873 g_assert(CALF_IS_COMBOBOX(widget));
875 if (gtk_widget_is_drawable (widget)) {
877 int padx = widget->style->xthickness;
878 int pady = widget->style->ythickness;
880 GtkComboBox *cb = GTK_COMBO_BOX(widget);
881 CalfCombobox *ccb = CALF_COMBOBOX(widget);
882 GdkWindow *window = widget->window;
883 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
885 GtkTreeModel *model = gtk_combo_box_get_model(cb);
886 GtkTreeIter iter;
887 gchar *lab;
888 if (gtk_combo_box_get_active_iter (cb, &iter))
889 gtk_tree_model_get (model, &iter, 0, &lab, -1);
890 else
891 lab = g_strdup("");
893 int x = widget->allocation.x;
894 int y = widget->allocation.y;
895 int sx = widget->allocation.width;
896 int sy = widget->allocation.height;
897 gint mx, my;
898 bool hover = false;
900 create_rectangle(c, x, y, sx, sy, 0);
901 cairo_clip(c);
903 gtk_widget_get_pointer(widget, &mx, &my);
904 if (mx >= 0 and mx < sx and my >= 0 and my < sy)
905 hover = true;
907 // background
908 float radius, bevel;
909 gtk_widget_style_get(widget, "border-radius", &radius, "bevel", &bevel, NULL);
910 display_background(widget, c, x, y, sx - padx * 2, sy - pady * 2, padx, pady, radius, bevel, g_ascii_isspace(lab[0]) ? 0 : 1, 4, hover ? 0.5 : 0, hover ? 0.1 : 0.25);
912 // text
913 gtk_container_propagate_expose (GTK_CONTAINER (widget),
914 GTK_BIN (widget)->child, event);
916 // arrow
917 if (ccb->arrow) {
918 int pw = gdk_pixbuf_get_width(ccb->arrow);
919 int ph = gdk_pixbuf_get_height(ccb->arrow);
920 gdk_draw_pixbuf(GDK_DRAWABLE(window), widget->style->fg_gc[0],
921 ccb->arrow, 0, 0,
922 x + sx - padx - pw, y + (sy - ph) / 2, pw, ph,
923 GDK_RGB_DITHER_NORMAL, 0, 0);
926 g_free(lab);
927 cairo_destroy(c);
929 return FALSE;
932 void
933 calf_combobox_set_arrow (CalfCombobox *self, GdkPixbuf *arrow)
935 self->arrow = arrow;
938 static void
939 calf_combobox_class_init (CalfComboboxClass *klass)
941 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
942 widget_class->expose_event = calf_combobox_expose;
943 gtk_widget_class_install_style_property(
944 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
945 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
946 gtk_widget_class_install_style_property(
947 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
948 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
951 static void
952 calf_combobox_init (CalfCombobox *self)
954 GtkWidget *widget = GTK_WIDGET(self);
955 widget->requisition.width = 40;
956 widget->requisition.height = 20;
959 GType
960 calf_combobox_get_type (void)
962 static GType type = 0;
963 if (!type) {
964 static const GTypeInfo type_info = {
965 sizeof(CalfComboboxClass),
966 NULL, /* base_init */
967 NULL, /* base_finalize */
968 (GClassInitFunc)calf_combobox_class_init,
969 NULL, /* class_finalize */
970 NULL, /* class_data */
971 sizeof(CalfCombobox),
972 0, /* n_preallocs */
973 (GInstanceInitFunc)calf_combobox_init
976 for (int i = 0; ; i++) {
977 //const char *name = "CalfCombobox";
978 char *name = g_strdup_printf("CalfCombobox%u%d",
979 ((unsigned int)(intptr_t)calf_combobox_class_init) >> 16, i);
980 if (g_type_from_name(name)) {
981 free(name);
982 continue;
984 type = g_type_register_static(GTK_TYPE_COMBO_BOX,
985 name,
986 &type_info,
987 (GTypeFlags)0);
988 free(name);
989 break;
992 return type;
996 ///////////////////////////////////////// notebook ///////////////////////////////////////////////
998 #define GTK_NOTEBOOK_PAGE(_glist_) ((GtkNotebookPage *)((GList *)(_glist_))->data)
999 struct _GtkNotebookPage
1001 GtkWidget *child;
1002 GtkWidget *tab_label;
1003 GtkWidget *menu_label;
1004 GtkWidget *last_focus_child; /* Last descendant of the page that had focus */
1006 guint default_menu : 1; /* If true, we create the menu label ourself */
1007 guint default_tab : 1; /* If true, we create the tab label ourself */
1008 guint expand : 1;
1009 guint fill : 1;
1010 guint pack : 1;
1011 guint reorderable : 1;
1012 guint detachable : 1;
1014 /* if true, the tab label was visible on last allocation; we track this so
1015 * that we know to redraw the tab area if a tab label was hidden then shown
1016 * without changing position */
1017 guint tab_allocated_visible : 1;
1019 GtkRequisition requisition;
1020 GtkAllocation allocation;
1022 gulong mnemonic_activate_signal;
1023 gulong notify_visible_handler;
1026 GtkWidget *
1027 calf_notebook_new()
1029 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_NOTEBOOK, NULL ));
1030 return widget;
1032 static gboolean
1033 calf_notebook_expose (GtkWidget *widget, GdkEventExpose *event)
1035 g_assert(CALF_IS_NOTEBOOK(widget));
1037 GtkNotebook *notebook = GTK_NOTEBOOK(widget);
1038 CalfNotebook *nb = CALF_NOTEBOOK(widget);
1040 if (gtk_widget_is_drawable (widget)) {
1042 GdkWindow *window = widget->window;
1043 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
1044 cairo_pattern_t *pat = NULL;
1046 int x = widget->allocation.x;
1047 int y = widget->allocation.y;
1048 int sx = widget->allocation.width;
1049 int sy = widget->allocation.height;
1050 int tx = widget->style->xthickness;
1051 int ty = widget->style->ythickness;
1052 int lh = 19;
1053 int bh = lh + 2 * ty;
1055 float r, g, b;
1056 float alpha;
1057 gtk_widget_style_get(widget, "background-alpha", &alpha, NULL);
1059 cairo_rectangle(c, x, y, sx, sy);
1060 cairo_clip(c);
1062 int add = 0;
1064 if (notebook->show_tabs) {
1065 GtkNotebookPage *page;
1066 GList *pages;
1068 gint sp;
1069 gtk_widget_style_get(widget, "tab-overlap", &sp, NULL);
1071 pages = notebook->children;
1073 int cn = 0;
1074 while (pages) {
1075 page = GTK_NOTEBOOK_PAGE (pages);
1076 pages = pages->next;
1077 if (page->tab_label->window == event->window &&
1078 gtk_widget_is_drawable (page->tab_label)) {
1079 int lx = page->tab_label->allocation.x;
1080 int lw = page->tab_label->allocation.width;
1082 // fix the labels position
1083 page->tab_label->allocation.y = y + ty;
1084 page->tab_label->allocation.height = lh;
1086 // draw tab background
1087 cairo_rectangle(c, lx - tx, y, lw + 2 * tx, bh);
1088 get_base_color(widget, NULL, &r, &g, &b);
1089 cairo_set_source_rgba(c, r,g,b, page != notebook->cur_page ? alpha / 2 : alpha);
1090 cairo_fill(c);
1092 if (page == notebook->cur_page) {
1093 // draw tab light
1094 get_bg_color(widget, NULL, &r, &g, &b);
1095 cairo_rectangle(c, lx - tx + 2, y + 2, lw + 2 * tx - 4, 2);
1096 cairo_set_source_rgb(c, r, g, b);
1097 cairo_fill(c);
1099 cairo_rectangle(c, lx - tx + 2, y + 1, lw + 2 * tx - 4, 1);
1100 cairo_set_source_rgba(c, 0,0,0,0.5);
1101 cairo_fill(c);
1103 cairo_rectangle(c, lx - tx + 2, y + 4, lw + 2 * tx - 4, 1);
1104 cairo_set_source_rgba(c, 1,1,1,0.3);
1105 cairo_fill(c);
1108 // draw labels
1109 gtk_container_propagate_expose (GTK_CONTAINER (notebook), page->tab_label, event);
1111 cn++;
1113 add = bh;
1116 // draw main body
1117 get_base_color(widget, NULL, &r, &g, &b);
1118 cairo_rectangle(c, x, y + add, sx, sy - add);
1119 cairo_set_source_rgba(c, r, g, b, alpha);
1120 cairo_fill(c);
1122 // draw frame
1123 cairo_rectangle(c, x + 0.5, y + add + 0.5, sx - 1, sy - add - 1);
1124 pat = cairo_pattern_create_linear(x, y + add, x, y + sy - add);
1125 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0.3);
1126 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0.5, 0.5, 0.5, 0);
1127 cairo_pattern_add_color_stop_rgba(pat, 1, 1, 1, 1, 0.2);
1128 cairo_set_source (c, pat);
1129 cairo_set_line_width(c, 1);
1130 cairo_stroke_preserve(c);
1132 int sw = gdk_pixbuf_get_width(nb->screw);
1133 int sh = gdk_pixbuf_get_height(nb->screw);
1135 // draw screws
1136 if (nb->screw) {
1137 gdk_cairo_set_source_pixbuf(c, nb->screw, x, y + add);
1138 cairo_fill_preserve(c);
1139 gdk_cairo_set_source_pixbuf(c, nb->screw, x + sx - sw, y + add);
1140 cairo_fill_preserve(c);
1141 gdk_cairo_set_source_pixbuf(c, nb->screw, x, y + sy - sh);
1142 cairo_fill_preserve(c);
1143 gdk_cairo_set_source_pixbuf(c, nb->screw, x + sx - sh, y + sy - sh);
1144 cairo_fill_preserve(c);
1146 // propagate expose to all children
1147 if (notebook->cur_page)
1148 gtk_container_propagate_expose (GTK_CONTAINER (notebook),
1149 notebook->cur_page->child,
1150 event);
1152 cairo_pattern_destroy(pat);
1153 cairo_destroy(c);
1156 return FALSE;
1159 void
1160 calf_notebook_set_pixbuf (CalfNotebook *self, GdkPixbuf *image)
1162 self->screw = image;
1163 gtk_widget_queue_draw(GTK_WIDGET(self));
1166 static void
1167 calf_notebook_class_init (CalfNotebookClass *klass)
1169 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1170 widget_class->expose_event = calf_notebook_expose;
1171 gtk_widget_class_install_style_property(
1172 widget_class, g_param_spec_float("background-alpha", "Alpha Background", "Alpha of background",
1173 0.0, 1.0, 0.5, GParamFlags(G_PARAM_READWRITE)));
1176 static void
1177 calf_notebook_init (CalfNotebook *self)
1179 //GtkWidget *widget = GTK_WIDGET(self);
1182 GType
1183 calf_notebook_get_type (void)
1185 static GType type = 0;
1186 if (!type) {
1187 static const GTypeInfo type_info = {
1188 sizeof(CalfNotebookClass),
1189 NULL, /* base_init */
1190 NULL, /* base_finalize */
1191 (GClassInitFunc)calf_notebook_class_init,
1192 NULL, /* class_finalize */
1193 NULL, /* class_data */
1194 sizeof(CalfNotebook),
1195 0, /* n_preallocs */
1196 (GInstanceInitFunc)calf_notebook_init
1199 for (int i = 0; ; i++) {
1200 //const char *name = "CalfNotebook";
1201 char *name = g_strdup_printf("CalfNotebook%u%d",
1202 ((unsigned int)(intptr_t)calf_notebook_class_init) >> 16, i);
1203 if (g_type_from_name(name)) {
1204 free(name);
1205 continue;
1207 type = g_type_register_static(GTK_TYPE_NOTEBOOK,
1208 name,
1209 &type_info,
1210 (GTypeFlags)0);
1211 free(name);
1212 break;
1215 return type;
1218 ///////////////////////////////////////// fader ///////////////////////////////////////////////
1220 void calf_fader_set_layout(GtkWidget *widget)
1222 GtkRange *range = GTK_RANGE(widget);
1223 CalfFader *fader = CALF_FADER(widget);
1224 CalfFaderLayout l = fader->layout;
1225 GdkRectangle t;
1226 gint sstart, send;
1228 gtk_range_get_range_rect(range, &t);
1229 gtk_range_get_slider_range(range, &sstart, &send);
1231 int hor = fader->horizontal;
1232 int slength;
1233 gtk_widget_style_get(widget, "slider-length", &slength, NULL);
1235 // widget layout
1236 l.x = widget->allocation.x + t.x;
1237 l.y = widget->allocation.y + t.y;
1238 l.w = t.width; //widget->allocation.width;
1239 l.h = t.height; //widget->allocation.height;
1241 // image layout
1242 l.iw = gdk_pixbuf_get_width(fader->image);
1243 l.ih = gdk_pixbuf_get_height(fader->image);
1245 // first screw layout
1246 l.s1w = hor ? slength : gdk_pixbuf_get_width(fader->image);
1247 l.s1h = !hor ? slength : gdk_pixbuf_get_height(fader->image);
1248 l.s1x1 = 0;
1249 l.s1y1 = 0;
1250 l.s1x2 = l.x;
1251 l.s1y2 = l.y;
1253 // second screw layout
1254 l.s2w = l.s1w;
1255 l.s2h = l.s1h;
1256 l.s2x1 = hor ? l.iw - 3 * l.s2w : 0;
1257 l.s2y1 = !hor ? l.ih - 3 * l.s2h : 0;
1258 l.s2x2 = hor ? l.w - l.s2w + l.x : l.x;
1259 l.s2y2 = !hor ? l.h - l.s2h + l.y : l.y;
1261 // trough 1 layout
1262 l.t1w = l.s1w;
1263 l.t1h = l.s1h;
1264 l.t1x1 = hor ? l.iw - 2 * l.s2w : 0;
1265 l.t1y1 = !hor ? l.ih - 2 * l.s2h : 0;
1267 // trough 2 layout
1268 l.t2w = l.s1w;
1269 l.t2h = l.s1h;
1270 l.t2x1 = hor ? l.iw - l.s2w : 0;
1271 l.t2y1 = !hor ? l.ih - l.s2h : 0;
1273 // slit layout
1274 l.sw = hor ? l.iw - 4 * l.s1w : l.ih;
1275 l.sh = !hor ? l.ih - 4 * l.s1h : l.iw;
1276 l.sx1 = hor ? l.s1w : 0;
1277 l.sy1 = !hor ? l.s1h : 0;
1278 l.sx2 = hor ? l.s1w + l.x : l.x;
1279 l.sy2 = !hor ? l.s1h + l.y : l.y;
1280 l.sw2 = hor ? l.w - 2 * l.s1w : l.iw;
1281 l.sh2 = !hor ? l.h - 2 * l.s1h : l.ih;
1283 fader->layout = l;
1286 GtkWidget *
1287 calf_fader_new(const int horiz = 0, const int size = 2, const double min = 0, const double max = 1, const double step = 0.1)
1289 GtkObject *adj;
1290 gint digits;
1292 adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
1294 if (fabs (step) >= 1.0 || step == 0.0)
1295 digits = 0;
1296 else
1297 digits = std::min(5, abs((gint) floor (log10 (fabs (step)))));
1299 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_FADER, NULL ));
1300 CalfFader *self = CALF_FADER(widget);
1302 GTK_RANGE(widget)->orientation = horiz ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1303 gtk_range_set_adjustment(GTK_RANGE(widget), GTK_ADJUSTMENT(adj));
1304 gtk_scale_set_digits(GTK_SCALE(widget), digits);
1306 self->size = size;
1307 self->horizontal = horiz;
1308 self->hover = 0;
1310 return widget;
1313 static bool calf_fader_hover(GtkWidget *widget)
1315 CalfFader *fader = CALF_FADER(widget);
1317 gint mx, my;
1318 gtk_widget_get_pointer(GTK_WIDGET(widget), &mx, &my);
1320 GtkRange *range = GTK_RANGE(widget);
1321 GdkRectangle trough;
1322 gint sstart, send;
1323 gtk_range_get_range_rect(range, &trough);
1324 gtk_range_get_slider_range(range, &sstart, &send);
1326 int hor = fader->horizontal;
1328 int x1 = hor ? sstart : trough.x;
1329 int x2 = hor ? send : trough.x + trough.width;
1330 int y1 = !hor ? sstart : trough.y;
1331 int y2 = !hor ? send : trough.y + trough.height;
1333 return mx >= x1 and mx <= x2 and my >= y1 and my <= y2;
1335 static void calf_fader_check_hover_change(GtkWidget *widget)
1337 CalfFader *fader = CALF_FADER(widget);
1338 bool hover = calf_fader_hover(widget);
1339 if (hover != fader->hover)
1340 gtk_widget_queue_draw(widget);
1341 fader->hover = hover;
1343 static gboolean
1344 calf_fader_motion (GtkWidget *widget, GdkEventMotion *event)
1346 calf_fader_check_hover_change(widget);
1347 return FALSE;
1350 static gboolean
1351 calf_fader_enter (GtkWidget *widget, GdkEventCrossing *event)
1353 calf_fader_check_hover_change(widget);
1354 return FALSE;
1357 static gboolean
1358 calf_fader_leave (GtkWidget *widget, GdkEventCrossing *event)
1360 CALF_FADER(widget)->hover = false;
1361 gtk_widget_queue_draw(widget);
1362 return FALSE;
1364 static void
1365 calf_fader_allocate (GtkWidget *widget, GtkAllocation *allocation)
1367 calf_fader_set_layout(widget);
1369 static void
1370 calf_fader_request (GtkWidget *widget, GtkAllocation *request)
1372 calf_fader_set_layout(widget);
1374 static gboolean
1375 calf_fader_expose (GtkWidget *widget, GdkEventExpose *event)
1377 g_assert(CALF_IS_FADER(widget));
1378 if (gtk_widget_is_drawable (widget)) {
1380 GdkWindow *window = widget->window;
1381 GtkScale *scale = GTK_SCALE(widget);
1382 GtkRange *range = GTK_RANGE(widget);
1383 CalfFader *fader = CALF_FADER(widget);
1384 CalfFaderLayout l = fader->layout;
1385 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
1386 int horiz = fader->horizontal;
1387 cairo_rectangle(c, l.x, l.y, l.w, l.h);
1388 cairo_clip(c);
1390 // position
1391 double r0 = range->adjustment->upper - range->adjustment->lower;
1392 double v0 = range->adjustment->value - range->adjustment->lower;
1393 if ((horiz and gtk_range_get_inverted(range))
1394 or (!horiz and gtk_range_get_inverted(range)))
1395 v0 = -v0 + r0;
1396 int vp = v0 / r0 * (horiz ? l.w - l.s1w : l.h - l.s1h);
1398 l.t1x2 = l.t2x2 = horiz ? l.x + vp : l.x;
1399 l.t1y2 = l.t2y2 = !horiz ? l.y + vp : l.y;
1401 GdkPixbuf *i = fader->image;
1403 // screw 1
1404 cairo_rectangle(c, l.s1x2, l.s1y2, l.s1w, l.s1h);
1405 gdk_cairo_set_source_pixbuf(c, i, l.s1x2 - l.s1x1, l.s1y2 - l.s1y1);
1406 cairo_fill(c);
1408 // screw 2
1409 cairo_rectangle(c, l.s2x2, l.s2y2, l.s2w, l.s2h);
1410 gdk_cairo_set_source_pixbuf(c, i, l.s2x2 - l.s2x1, l.s2y2 - l.s2y1);
1411 cairo_fill(c);
1413 // trough
1414 if (horiz) {
1415 int x = l.sx2;
1416 while (x < l.sx2 + l.sw2) {
1417 cairo_rectangle(c, x, l.sy2, std::min(l.sx2 + l.sw2 - x, l.sw), l.sh2);
1418 gdk_cairo_set_source_pixbuf(c, i, x - l.sx1, l.sy2 - l.sy1);
1419 cairo_fill(c);
1420 x += l.sw;
1422 } else {
1423 int y = l.sy2;
1424 while (y < l.sy2 + l.sh2) {
1425 cairo_rectangle(c, l.sx2, y, l.sw2, std::min(l.sy2 + l.sh2 - y, l.sh));
1426 gdk_cairo_set_source_pixbuf(c, i, l.sx2 - l.sx1, y - l.sy1);
1427 cairo_fill(c);
1428 y += l.sh;
1432 // slider
1433 if (fader->hover or widget->state == GTK_STATE_ACTIVE) {
1434 cairo_rectangle(c, l.t1x2, l.t1y2, l.t1w, l.t1h);
1435 gdk_cairo_set_source_pixbuf(c, i, l.t1x2 - l.t1x1, l.t1y2 - l.t1y1);
1436 } else {
1437 cairo_rectangle(c, l.t2x2, l.t2y2, l.t2w, l.t2h);
1438 gdk_cairo_set_source_pixbuf(c, i, l.t2x2 - l.t2x1, l.t2y2 - l.t2y1);
1440 cairo_fill(c);
1443 // draw value label
1444 if (scale->draw_value) {
1445 PangoLayout *layout;
1446 gint _x, _y;
1447 layout = gtk_scale_get_layout (scale);
1448 gtk_scale_get_layout_offsets (scale, &_x, &_y);
1449 gtk_paint_layout (widget->style, window, GTK_STATE_NORMAL, FALSE, NULL,
1450 widget, horiz ? "hscale" : "vscale", _x, _y, layout);
1453 cairo_destroy(c);
1455 return FALSE;
1458 void
1459 calf_fader_set_pixbuf (CalfFader *self, GdkPixbuf *image)
1461 GtkWidget *widget = GTK_WIDGET(self);
1462 self->image = image;
1463 gtk_widget_queue_resize(widget);
1466 static void
1467 calf_fader_class_init (CalfFaderClass *klass)
1469 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1470 widget_class->expose_event = calf_fader_expose;
1473 static void
1474 calf_fader_init (CalfFader *self)
1476 GtkWidget *widget = GTK_WIDGET(self);
1477 widget->requisition.width = 40;
1478 widget->requisition.height = 40;
1480 gtk_signal_connect(GTK_OBJECT(widget), "motion-notify-event", GTK_SIGNAL_FUNC (calf_fader_motion), NULL);
1481 gtk_signal_connect(GTK_OBJECT(widget), "enter-notify-event", GTK_SIGNAL_FUNC (calf_fader_enter), NULL);
1482 gtk_signal_connect(GTK_OBJECT(widget), "leave-notify-event", GTK_SIGNAL_FUNC (calf_fader_leave), NULL);
1483 gtk_signal_connect(GTK_OBJECT(widget), "size-allocate", GTK_SIGNAL_FUNC (calf_fader_allocate), NULL);
1484 gtk_signal_connect(GTK_OBJECT(widget), "size-request", GTK_SIGNAL_FUNC (calf_fader_request), NULL);
1487 GType
1488 calf_fader_get_type (void)
1490 static GType type = 0;
1491 if (!type) {
1492 static const GTypeInfo type_info = {
1493 sizeof(CalfFaderClass),
1494 NULL, /* base_init */
1495 NULL, /* base_finalize */
1496 (GClassInitFunc)calf_fader_class_init,
1497 NULL, /* class_finalize */
1498 NULL, /* class_data */
1499 sizeof(CalfFader),
1500 0, /* n_preallocs */
1501 (GInstanceInitFunc)calf_fader_init
1504 for (int i = 0; ; i++) {
1505 char *name = g_strdup_printf("CalfFader%u%d",
1506 ((unsigned int)(intptr_t)calf_fader_class_init) >> 16, i);
1507 if (g_type_from_name(name)) {
1508 free(name);
1509 continue;
1511 type = g_type_register_static(GTK_TYPE_SCALE,
1512 name,
1513 &type_info,
1514 (GTypeFlags)0);
1515 free(name);
1516 break;
1519 return type;
1523 ///////////////////////////////////////// button ///////////////////////////////////////////////
1525 GtkWidget *
1526 calf_button_new(const gchar *label)
1528 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_BUTTON, NULL ));
1529 gtk_button_set_label(GTK_BUTTON(widget), label);
1530 return widget;
1532 static gboolean
1533 calf_button_expose (GtkWidget *widget, GdkEventExpose *event)
1535 g_assert(CALF_IS_BUTTON(widget) || CALF_IS_TOGGLE_BUTTON(widget) || CALF_IS_RADIO_BUTTON(widget));
1537 if (gtk_widget_is_drawable (widget)) {
1539 GdkWindow *window = widget->window;
1540 GtkWidget *child = GTK_BIN (widget)->child;
1541 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
1543 int x = widget->allocation.x;
1544 int y = widget->allocation.y;
1545 int sx = widget->allocation.width;
1546 int sy = widget->allocation.height;
1547 int ox = widget->style->xthickness;
1548 int oy = widget->style->ythickness;
1549 int bx = x + ox + 1;
1550 int by = y + oy + 1;
1551 int bw = sx - 2 * ox - 2;
1552 int bh = sy - 2 * oy - 2;
1554 float r, g, b;
1555 float radius, bevel, inset;
1556 GtkBorder *border;
1558 cairo_rectangle(c, x, y, sx, sy);
1559 cairo_clip(c);
1561 get_bg_color(widget, NULL, &r, &g, &b);
1562 gtk_widget_style_get(widget, "border-radius", &radius, "bevel", &bevel, "inset", &inset, NULL);
1563 gtk_widget_style_get(widget, "inner-border", &border, NULL);
1565 // inset
1566 draw_bevel(c, x, y, sx, sy, radius, inset*-1);
1568 // space
1569 create_rectangle(c, x + ox, y + oy, sx - ox * 2, sy - oy * 2, std::max(0.f, radius - ox));
1570 cairo_set_source_rgba(c, 0, 0, 0, 0.6);
1571 cairo_fill(c);
1573 // button
1574 create_rectangle(c, bx, by, bw, bh, std::max(0.f, radius - ox - 1));
1575 cairo_set_source_rgb(c, r, g, b);
1576 cairo_fill(c);
1577 draw_bevel(c, bx, by, bw, bh, std::max(0.f, radius - ox - 1), bevel);
1579 // pin
1580 if (CALF_IS_TOGGLE_BUTTON(widget) or CALF_IS_RADIO_BUTTON(widget)) {
1581 int pinh;
1582 int pinm = 6;
1583 gtk_widget_style_get(widget, "indicator", &pinh, NULL);
1584 get_text_color(widget, NULL, &r, &g, &b);
1585 float a;
1586 if (widget->state == GTK_STATE_PRELIGHT)
1587 gtk_widget_style_get(widget, "alpha-prelight", &a, NULL);
1588 else if (widget->state == GTK_STATE_ACTIVE)
1589 gtk_widget_style_get(widget, "alpha-active", &a, NULL);
1590 else
1591 gtk_widget_style_get(widget, "alpha-normal", &a, NULL);
1592 cairo_rectangle(c, x + sx - border->right - ox + pinm, y + sy / 2 - pinh / 2,
1593 border->right - pinm * 2 - ox, pinh);
1594 cairo_set_source_rgba(c, r, g, b, a);
1595 cairo_fill(c);
1598 cairo_destroy(c);
1599 gtk_container_propagate_expose (GTK_CONTAINER (widget), child, event);
1601 return FALSE;
1604 static void
1605 calf_button_class_init (CalfButtonClass *klass)
1607 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1608 widget_class->expose_event = calf_button_expose;
1609 gtk_widget_class_install_style_property(
1610 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
1611 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
1612 gtk_widget_class_install_style_property(
1613 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
1614 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
1615 gtk_widget_class_install_style_property(
1616 widget_class, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
1617 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1618 gtk_widget_class_install_style_property(
1619 widget_class, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
1620 0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE)));
1621 gtk_widget_class_install_style_property(
1622 widget_class, g_param_spec_float("alpha-active", "Alpha Active", "Alpha of ring in active state",
1623 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1624 gtk_widget_class_install_style_property(
1625 widget_class, g_param_spec_float("inset", "Inset", "Amount of inset effect",
1626 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1629 static void
1630 calf_button_init (CalfButton *self)
1632 GtkWidget *widget = GTK_WIDGET(self);
1633 widget->requisition.width = 40;
1634 widget->requisition.height = 20;
1637 GType
1638 calf_button_get_type (void)
1640 static GType type = 0;
1641 if (!type) {
1642 static const GTypeInfo type_info = {
1643 sizeof(CalfButtonClass),
1644 NULL, /* base_init */
1645 NULL, /* base_finalize */
1646 (GClassInitFunc)calf_button_class_init,
1647 NULL, /* class_finalize */
1648 NULL, /* class_data */
1649 sizeof(CalfButton),
1650 0, /* n_preallocs */
1651 (GInstanceInitFunc)calf_button_init
1654 for (int i = 0; ; i++) {
1655 //const char *name = "CalfButton";
1656 char *name = g_strdup_printf("CalfButton%u%d",
1657 ((unsigned int)(intptr_t)calf_button_class_init) >> 16, i);
1658 if (g_type_from_name(name)) {
1659 free(name);
1660 continue;
1662 type = g_type_register_static(GTK_TYPE_BUTTON,
1663 name,
1664 &type_info,
1665 (GTypeFlags)0);
1666 free(name);
1667 break;
1670 return type;
1674 ///////////////////////////////////////// toggle button ///////////////////////////////////////////////
1676 GtkWidget *
1677 calf_toggle_button_new(const gchar *label)
1679 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TOGGLE_BUTTON, NULL ));
1680 gtk_button_set_label(GTK_BUTTON(widget), label);
1681 return widget;
1684 static void
1685 calf_toggle_button_class_init (CalfToggleButtonClass *klass)
1687 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1688 widget_class->expose_event = calf_button_expose;
1689 gtk_widget_class_install_style_property(
1690 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
1691 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
1692 gtk_widget_class_install_style_property(
1693 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
1694 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
1695 gtk_widget_class_install_style_property(
1696 widget_class, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
1697 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1698 gtk_widget_class_install_style_property(
1699 widget_class, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
1700 0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE)));
1701 gtk_widget_class_install_style_property(
1702 widget_class, g_param_spec_float("alpha-active", "Alpha Active", "Alpha of ring in active state",
1703 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1704 gtk_widget_class_install_style_property(
1705 widget_class, g_param_spec_float("inset", "Inset", "Amount of inset effect",
1706 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1707 gtk_widget_class_install_style_property(
1708 widget_class, g_param_spec_int("indicator", "Indicator", "Height of indicator",
1709 0, 20, 3, GParamFlags(G_PARAM_READWRITE)));
1712 static void
1713 calf_toggle_button_init (CalfToggleButton *self)
1715 GtkWidget *widget = GTK_WIDGET(self);
1716 widget->requisition.width = 40;
1717 widget->requisition.height = 20;
1720 GType
1721 calf_toggle_button_get_type (void)
1723 static GType type = 0;
1724 if (!type) {
1725 static const GTypeInfo type_info = {
1726 sizeof(CalfToggleButtonClass),
1727 NULL, /* base_init */
1728 NULL, /* base_finalize */
1729 (GClassInitFunc)calf_toggle_button_class_init,
1730 NULL, /* class_finalize */
1731 NULL, /* class_data */
1732 sizeof(CalfToggleButton),
1733 0, /* n_preallocs */
1734 (GInstanceInitFunc)calf_toggle_button_init
1737 for (int i = 0; ; i++) {
1738 //const char *name = "CalfToggleButton";
1739 char *name = g_strdup_printf("CalfToggleButton%u%d",
1740 ((unsigned int)(intptr_t)calf_toggle_button_class_init) >> 16, i);
1741 if (g_type_from_name(name)) {
1742 free(name);
1743 continue;
1745 type = g_type_register_static(GTK_TYPE_TOGGLE_BUTTON,
1746 name,
1747 &type_info,
1748 (GTypeFlags)0);
1749 free(name);
1750 break;
1753 return type;
1756 ///////////////////////////////////////// radio button ///////////////////////////////////////////////
1758 GtkWidget *
1759 calf_radio_button_new(const gchar *label)
1761 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_RADIO_BUTTON, NULL ));
1762 gtk_button_set_label(GTK_BUTTON(widget), label);
1763 return widget;
1766 static void
1767 calf_radio_button_class_init (CalfRadioButtonClass *klass)
1769 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1770 widget_class->expose_event = calf_button_expose;
1771 gtk_widget_class_install_style_property(
1772 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
1773 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
1774 gtk_widget_class_install_style_property(
1775 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
1776 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
1777 gtk_widget_class_install_style_property(
1778 widget_class, g_param_spec_float("alpha-normal", "Alpha Normal", "Alpha of ring in normal state",
1779 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1780 gtk_widget_class_install_style_property(
1781 widget_class, g_param_spec_float("alpha-prelight", "Alpha Prelight", "Alpha of ring in prelight state",
1782 0.0, 1.0, 1.0, GParamFlags(G_PARAM_READWRITE)));
1783 gtk_widget_class_install_style_property(
1784 widget_class, g_param_spec_float("alpha-active", "Alpha Active", "Alpha of ring in active state",
1785 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1786 gtk_widget_class_install_style_property(
1787 widget_class, g_param_spec_float("inset", "Inset", "Amount of inset effect",
1788 0.0, 1.0, 0.2, GParamFlags(G_PARAM_READWRITE)));
1789 gtk_widget_class_install_style_property(
1790 widget_class, g_param_spec_int("indicator", "Indicator", "Height of indicator",
1791 0, 20, 3, GParamFlags(G_PARAM_READWRITE)));
1794 static void
1795 calf_radio_button_init (CalfRadioButton *self)
1797 GtkWidget *widget = GTK_WIDGET(self);
1798 widget->requisition.width = 40;
1799 widget->requisition.height = 20;
1802 GType
1803 calf_radio_button_get_type (void)
1805 static GType type = 0;
1806 if (!type) {
1807 static const GTypeInfo type_info = {
1808 sizeof(CalfRadioButtonClass),
1809 NULL, /* base_init */
1810 NULL, /* base_finalize */
1811 (GClassInitFunc)calf_radio_button_class_init,
1812 NULL, /* class_finalize */
1813 NULL, /* class_data */
1814 sizeof(CalfRadioButton),
1815 0, /* n_preallocs */
1816 (GInstanceInitFunc)calf_radio_button_init
1819 for (int i = 0; ; i++) {
1820 //const char *name = "CalfRadioButton";
1821 char *name = g_strdup_printf("CalfRadioButton%u%d",
1822 ((unsigned int)(intptr_t)calf_radio_button_class_init) >> 16, i);
1823 if (g_type_from_name(name)) {
1824 free(name);
1825 continue;
1827 type = g_type_register_static(GTK_TYPE_RADIO_BUTTON,
1828 name,
1829 &type_info,
1830 (GTypeFlags)0);
1831 free(name);
1832 break;
1835 return type;
1838 ///////////////////////////////////////// tap button ///////////////////////////////////////////////
1840 GtkWidget *
1841 calf_tap_button_new()
1843 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TAP_BUTTON, NULL ));
1844 return widget;
1847 static gboolean
1848 calf_tap_button_expose (GtkWidget *widget, GdkEventExpose *event)
1850 g_assert(CALF_IS_TAP_BUTTON(widget));
1851 CalfTapButton *self = CALF_TAP_BUTTON(widget);
1853 if (!self->image[self->state])
1854 return FALSE;
1856 int width = gdk_pixbuf_get_width(self->image[0]);
1857 int height = gdk_pixbuf_get_height(self->image[0]);
1858 int x = widget->allocation.x + widget->allocation.width / 2 - width / 2;
1859 int y = widget->allocation.y + widget->allocation.height / 2 - height / 2;
1861 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window),
1862 widget->style->fg_gc[0],
1863 self->image[self->state],
1868 width,
1869 height,
1870 GDK_RGB_DITHER_NORMAL, 0, 0);
1871 return TRUE;
1874 void
1875 calf_tap_button_set_pixbufs (CalfTapButton *self, GdkPixbuf *image1, GdkPixbuf *image2, GdkPixbuf *image3)
1877 GtkWidget *widget = GTK_WIDGET(self);
1878 self->image[0] = image1;
1879 self->image[1] = image2;
1880 self->image[2] = image3;
1881 widget->requisition.width = gdk_pixbuf_get_width(self->image[0]);
1882 widget->requisition.height = gdk_pixbuf_get_height(self->image[0]);
1883 gtk_widget_queue_resize(widget);
1886 static void
1887 calf_tap_button_size_request (GtkWidget *widget,
1888 GtkRequisition *requisition)
1890 g_assert(CALF_IS_TAP_BUTTON(widget));
1891 requisition->width = 70;
1892 requisition->height = 70;
1894 static void
1895 calf_tap_button_class_init (CalfTapButtonClass *klass)
1897 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1898 widget_class->expose_event = calf_tap_button_expose;
1899 widget_class->size_request = calf_tap_button_size_request;
1901 static void
1902 calf_tap_button_init (CalfTapButton *self)
1904 self->state = 0;
1907 GType
1908 calf_tap_button_get_type (void)
1910 static GType type = 0;
1911 if (!type) {
1912 static const GTypeInfo type_info = {
1913 sizeof(CalfTapButtonClass),
1914 NULL, /* base_init */
1915 NULL, /* base_finalize */
1916 (GClassInitFunc)calf_tap_button_class_init,
1917 NULL, /* class_finalize */
1918 NULL, /* class_data */
1919 sizeof(CalfTapButton),
1920 0, /* n_preallocs */
1921 (GInstanceInitFunc)calf_tap_button_init
1924 for (int i = 0; ; i++) {
1925 char *name = g_strdup_printf("CalfTapButton%u%d",
1926 ((unsigned int)(intptr_t)calf_tap_button_class_init) >> 16, i);
1927 if (g_type_from_name(name)) {
1928 free(name);
1929 continue;
1931 type = g_type_register_static(GTK_TYPE_BUTTON,
1932 name,
1933 &type_info,
1934 (GTypeFlags)0);
1935 free(name);
1936 break;
1939 return type;
1943 ///////////////////////////////////////// tuner ///////////////////////////////////////////////
1945 static void calf_tuner_create_dot(cairo_t *ctx, int dots, int dot, float rad)
1947 cairo_save(ctx);
1948 cairo_rotate(ctx, dot * M_PI / (dots * 8) * 2);
1949 cairo_move_to(ctx, 0, -rad);
1950 cairo_line_to(ctx, 0, 0);
1951 cairo_stroke(ctx);
1952 cairo_restore(ctx);
1955 static void
1956 calf_tuner_draw_background( GtkWidget *widget, cairo_t *ctx, int sx, int sy, int ox, int oy )
1958 int dw = 2;
1959 int dm = 1;
1960 int x0 = ox + 0.025;
1961 int x1 = ox + sx - 0.025;
1962 int a = x1 - x0;
1963 int dots = a * 0.5 / (dw + dm);
1964 float rad = sqrt(2.f) / 2.f * a;
1965 int cx = ox + sx / 2;
1966 int cy = ox + sy / 2;
1967 int ccy = cy - sy / 3 + rad;
1969 display_background(widget, ctx, 0, 0, sx, sy, ox, oy);
1970 cairo_stroke(ctx);
1971 cairo_save(ctx);
1973 cairo_rectangle(ctx, ox * 2, oy * 2, sx - 2 * ox, sy - 2 * oy);
1974 cairo_clip(ctx);
1976 cairo_set_source_rgba(ctx, 0.35, 0.4, 0.2, 0.3);
1977 cairo_set_line_width(ctx, dw);
1978 cairo_translate(ctx, cx, ccy);
1980 for(int i = 2; i < dots + 2; i++) {
1981 calf_tuner_create_dot(ctx, dots, i, rad);
1983 for(int i = -2; i > -dots - 2; i--) {
1984 calf_tuner_create_dot(ctx, dots, i, rad);
1986 cairo_set_line_width(ctx, dw * 3);
1987 calf_tuner_create_dot(ctx, dots, 0, rad);
1990 static void calf_tuner_draw_dot(cairo_t * ctx, float cents, int sx, int sy, int ox, int oy)
1992 cairo_rectangle(ctx, ox * 2, oy * 2, sx - 2 * ox, sy - 2 * oy);
1993 cairo_clip(ctx);
1995 int dw = 2;
1996 int dm = 1;
1997 int x0 = ox + 0.025;
1998 int x1 = ox + sx - 0.025;
1999 int a = x1 - x0;
2000 int dots = a * 0.5 / (dw + dm);
2001 int dot = cents * 2.f * dots;
2002 float rad = sqrt(2.f) / 2.f * a;
2003 int cx = ox + sx / 2;
2004 int cy = ox + sy / 2;
2005 int ccy = cy - sy / 3 + rad;
2007 int sign = (dot > 0) - (dot < 0);
2008 int marg = dot ? sign : 0;
2009 cairo_save(ctx);
2010 cairo_set_source_rgba(ctx, 0.35, 0.4, 0.2, 0.9);
2011 cairo_translate(ctx, cx, ccy);
2012 cairo_set_line_width(ctx, dw * (dot ? 1 : 3));
2013 calf_tuner_create_dot(ctx, dots, dot + marg, rad);
2014 cairo_restore(ctx);
2017 static gboolean
2018 calf_tuner_expose (GtkWidget *widget, GdkEventExpose *event)
2020 g_assert(CALF_IS_TUNER(widget));
2021 CalfTuner *tuner = CALF_TUNER(widget);
2023 //printf("%d %f\n", tuner->note, tuner->cents);
2025 // dimensions
2026 int ox = 5, oy = 5;
2027 int sx = widget->allocation.width - ox * 2, sy = widget->allocation.height - oy * 2;
2028 int marg = 10;
2029 int fpt = 9;
2030 float fsize = fpt * sy / 25; // 9pt @ 25px height
2032 // cairo initialization stuff
2033 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
2034 cairo_t *ctx_back;
2036 if( tuner->background == NULL ) {
2037 // looks like its either first call or the widget has been resized.
2038 // create the background surface (stolen from line graph)...
2039 cairo_surface_t *window_surface = cairo_get_target(c);
2040 tuner->background = cairo_surface_create_similar(window_surface,
2041 CAIRO_CONTENT_COLOR,
2042 widget->allocation.width,
2043 widget->allocation.height );
2045 // ...and draw some bling bling onto it...
2046 ctx_back = cairo_create(tuner->background);
2047 calf_tuner_draw_background(widget, ctx_back, sx, sy, ox, oy);
2048 } else {
2049 ctx_back = cairo_create(tuner->background);
2052 cairo_set_source_surface(c, cairo_get_target(ctx_back), 0, 0);
2053 cairo_paint(c);
2055 calf_tuner_draw_dot(c, tuner->cents / 100, sx, sy, ox, oy);
2057 static const char notenames[] = "C\0\0C#\0D\0\0D#\0E\0\0F\0\0F#\0G\0\0G#\0A\0\0A#\0B\0\0";
2058 const char * note = notenames + (tuner->note % 12) * 3;
2059 int oct = int(tuner->note / 12) - 2;
2060 cairo_set_source_rgba(c, 0.35, 0.4, 0.2, 0.9);
2061 cairo_text_extents_t te;
2062 if (tuner->note) {
2063 // Note name
2064 cairo_select_font_face(c, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
2065 cairo_set_font_size(c, fsize);
2066 cairo_text_extents (c, note, &te);
2067 cairo_move_to (c, ox + marg - te.x_bearing, oy + marg - te.y_bearing);
2068 cairo_show_text (c, note);
2069 // octave
2070 char octn[4];
2071 sprintf(octn, "%d", oct);
2072 cairo_set_font_size(c, fsize / 2);
2073 cairo_text_extents (c, octn, &te);
2074 cairo_show_text(c, octn);
2075 // right hand side
2076 cairo_set_font_size(c, fsize / 4);
2077 cairo_select_font_face(c, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
2078 const char * mnotet = "MIDI Note: ";
2079 char mnotev[32];
2080 sprintf(mnotev, "%d", tuner->note);
2081 const char * centst = "Cents: ";
2082 char centsv[32];
2083 sprintf(centsv, "%.4f", tuner->cents);
2085 // calc text extents
2086 cairo_text_extents (c, mnotet, &te);
2087 int mtw = te.width;
2088 cairo_text_extents (c, "999", &te);
2089 int mvw = te.width;
2090 cairo_text_extents (c, centst, &te);
2091 int ctw = te.width;
2092 cairo_text_extents (c, "-9.9999", &te);
2093 int cvw = te.width;
2094 float xb = te.x_bearing;
2096 float tw = std::max(ctw, mtw);
2097 float vw = std::max(cvw, mvw);
2099 // draw MIDI note
2100 cairo_move_to(c, ox + sx - tw - vw - marg * 2, oy + marg - te.y_bearing);
2101 cairo_show_text(c, mnotet);
2102 cairo_move_to(c, ox + sx - vw - xb - marg, oy + marg - te.y_bearing);
2103 cairo_show_text(c, mnotev);
2104 // draw cents
2105 cairo_move_to(c, ox + sx - tw - vw - marg * 2, oy + marg + te.height + 5 - te.y_bearing);
2106 cairo_show_text(c, centst);
2107 cairo_move_to(c, ox + sx - vw - xb - marg, oy + marg + te.height + 5 - te.y_bearing);
2108 cairo_show_text(c, centsv);
2111 cairo_destroy(c);
2112 cairo_destroy(ctx_back);
2113 return TRUE;
2116 static void
2117 calf_tuner_size_request (GtkWidget *widget,
2118 GtkRequisition *requisition)
2120 g_assert(CALF_IS_TUNER(widget));
2121 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
2124 static void
2125 calf_tuner_size_allocate (GtkWidget *widget,
2126 GtkAllocation *allocation)
2128 g_assert(CALF_IS_TUNER(widget));
2129 CalfTuner *lg = CALF_TUNER(widget);
2131 if(lg->background)
2132 cairo_surface_destroy(lg->background);
2133 lg->background = NULL;
2135 widget->allocation = *allocation;
2138 static void
2139 calf_tuner_class_init (CalfTunerClass *klass)
2141 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
2142 widget_class->expose_event = calf_tuner_expose;
2143 widget_class->size_request = calf_tuner_size_request;
2144 widget_class->size_allocate = calf_tuner_size_allocate;
2147 static void
2148 calf_tuner_unrealize (GtkWidget *widget, CalfTuner *tuner)
2150 if( tuner->background )
2151 cairo_surface_destroy(tuner->background);
2152 tuner->background = NULL;
2155 static void
2156 calf_tuner_init (CalfTuner *self)
2158 GtkWidget *widget = GTK_WIDGET(self);
2159 widget->requisition.width = 40;
2160 widget->requisition.height = 40;
2161 self->background = NULL;
2162 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_tuner_unrealize), (gpointer)self);
2165 GtkWidget *
2166 calf_tuner_new()
2168 return GTK_WIDGET(g_object_new (CALF_TYPE_TUNER, NULL));
2171 GType
2172 calf_tuner_get_type (void)
2174 static GType type = 0;
2175 if (!type) {
2176 static const GTypeInfo type_info = {
2177 sizeof(CalfTunerClass),
2178 NULL, /* base_init */
2179 NULL, /* base_finalize */
2180 (GClassInitFunc)calf_tuner_class_init,
2181 NULL, /* class_finalize */
2182 NULL, /* class_data */
2183 sizeof(CalfTuner),
2184 0, /* n_preallocs */
2185 (GInstanceInitFunc)calf_tuner_init
2188 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
2190 for (int i = 0; ; i++) {
2191 char *name = g_strdup_printf("CalfTuner%u%d", ((unsigned int)(intptr_t)calf_tuner_class_init) >> 16, i);
2192 if (g_type_from_name(name)) {
2193 free(name);
2194 continue;
2196 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
2197 name,
2198 type_info_copy,
2199 (GTypeFlags)0);
2200 free(name);
2201 break;
2204 return type;