+ JACK host: add open/save dialogs (still no implementation of the actual open/save...
[calf.git] / src / custom_ctl.cpp
blob66202847e98dc913dafcc147567e249d6fd95c20
1 /* Calf DSP Library
2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007 Krzysztof Foltman
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
22 #include "config.h"
23 #include <calf/custom_ctl.h>
24 #include <gdk/gdkkeysyms.h>
25 #include <cairo/cairo.h>
26 #include <math.h>
27 #include <gdk/gdk.h>
28 #include <sys/time.h>
31 I don't really know how to do it, or if it can be done this way.
32 struct calf_ui_type_module
34 GTypeModule *module;
36 calf_ui_type_module()
38 module = g_type_module_new();
39 g_type_module_set_name(module, "calf_custom_ctl");
40 g_type_module_use(module);
42 ~calf_ui_type_module()
44 g_type_module_unuse(module);
48 static calf_ui_type_module type_module;
51 static void
52 calf_line_graph_copy_cache_to_window( CalfLineGraph *lg, cairo_t *c )
54 cairo_save( c );
55 cairo_set_source_surface( c, lg->cache_surface, 0,0 );
56 cairo_paint( c );
57 cairo_restore( c );
60 static void
61 calf_line_graph_copy_window_to_cache( CalfLineGraph *lg, cairo_t *c )
63 cairo_t *cache_cr = cairo_create( lg->cache_surface );
64 cairo_surface_t *window_surface = cairo_get_target( c );
65 cairo_set_source_surface( cache_cr, window_surface, 0,0 );
66 cairo_paint( cache_cr );
67 cairo_destroy( cache_cr );
70 static void
71 calf_line_graph_draw_grid( cairo_t *c, std::string &legend, bool vertical, float pos, int phase, int sx, int sy )
73 int ox=5, oy=5;
74 cairo_text_extents_t tx;
75 if (!legend.empty())
76 cairo_text_extents(c, legend.c_str(), &tx);
77 if (vertical)
79 float x = floor(ox + pos * sx) + 0.5;
80 if (phase == 1)
82 cairo_move_to(c, x, oy);
83 cairo_line_to(c, x, oy + sy);
84 cairo_stroke(c);
86 if (phase == 2 && !legend.empty()) {
88 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 0.7);
89 cairo_move_to(c, x - (tx.x_bearing + tx.width / 2.0) - 2, oy + sy - 2);
90 cairo_show_text(c, legend.c_str());
93 else
95 float y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
96 if (phase == 1)
98 cairo_move_to(c, ox, y);
99 cairo_line_to(c, ox + sx, y);
100 cairo_stroke(c);
102 if (phase == 2 && !legend.empty()) {
103 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 0.7);
104 cairo_move_to(c, ox + sx - 4 - tx.width, y + tx.height/2);
105 cairo_show_text(c, legend.c_str());
110 static void
111 calf_line_graph_draw_graph( cairo_t *c, float *data, int sx, int sy )
113 int ox=5, oy=5;
115 for (int i = 0; i < 2 * sx; i++)
117 int y = (int)(oy + sy / 2 - (sy / 2 - 1) * data[i]);
118 //if (y < oy) y = oy;
119 //if (y >= oy + sy) y = oy + sy - 1;
120 if (i)
121 cairo_line_to(c, ox + i * 0.5, y);
122 else
123 cairo_move_to(c, ox, y);
125 cairo_stroke(c);
128 static gboolean
129 calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
131 g_assert(CALF_IS_LINE_GRAPH(widget));
133 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
134 //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
135 int ox = 5, oy = 5, rad, pad;
136 int sx = widget->allocation.width - ox * 2, sy = widget->allocation.height - oy * 2;
138 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
139 GtkStyle *style;
140 style = gtk_widget_get_style(widget);
141 GdkColor sc = { 0, 0, 0, 0 };
143 bool cache_dirty = 0;
145 if( lg->cache_surface == NULL ) {
146 // looks like its either first call or the widget has been resized.
147 // create the cache_surface.
148 cairo_surface_t *window_surface = cairo_get_target( c );
149 lg->cache_surface = cairo_surface_create_similar( window_surface,
150 CAIRO_CONTENT_COLOR,
151 widget->allocation.width,
152 widget->allocation.height );
153 //cairo_set_source_surface( cache_cr, window_surface, 0,0 );
154 //cairo_paint( cache_cr );
156 cache_dirty = 1;
159 cairo_select_font_face(c, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
160 cairo_set_font_size(c, 9);
161 gdk_cairo_set_source_color(c, &sc);
163 cairo_impl cimpl;
164 cimpl.context = c;
166 if (lg->source) {
169 float pos = 0;
170 bool vertical = false;
171 std::string legend;
172 float *data = new float[2 * sx];
173 GdkColor sc2 = { 0, 0.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
174 float x, y;
175 int size = 0;
176 GdkColor sc3 = { 0, 0.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
178 int graph_n, grid_n, dot_n, grid_n_save;
180 int cache_graph_index, cache_dot_index, cache_grid_index;
181 int gen_index = lg->source->get_changed_offsets( lg->source_id, lg->last_generation, cache_graph_index, cache_dot_index, cache_grid_index );
183 if( cache_dirty || (gen_index != lg->last_generation) ) {
185 cairo_t *cache_cr = cairo_create( lg->cache_surface );
187 // if(widget->style->bg_pixmap[0] == NULL) {
188 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
189 // } else {
190 // gdk_cairo_set_source_pixbuf(cache_cr, GDK_PIXBUF(&style->bg_pixmap[GTK_STATE_NORMAL]), widget->allocation.x, widget->allocation.y + 20);
191 // }
192 cairo_paint(cache_cr);
194 // outer (light)
195 pad = 0;
196 rad = 6;
197 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
198 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
199 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
200 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
201 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
202 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
203 cairo_pattern_t *pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
204 cairo_pattern_add_color_stop_rgba (pat2, 0, 0, 0, 0, 0.3);
205 cairo_pattern_add_color_stop_rgba (pat2, 1, 1, 1, 1, 0.6);
206 cairo_set_source (cache_cr, pat2);
207 cairo_fill(cache_cr);
209 // inner (black)
210 pad = 1;
211 rad = 5;
212 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
213 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
214 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
215 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
216 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
217 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
218 pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
219 cairo_pattern_add_color_stop_rgba (pat2, 0, 0.23, 0.23, 0.23, 1);
220 cairo_pattern_add_color_stop_rgba (pat2, 0.5, 0, 0, 0, 1);
221 cairo_set_source (cache_cr, pat2);
222 cairo_fill(cache_cr);
223 cairo_pattern_destroy(pat2);
225 cairo_rectangle(cache_cr, ox - 1, oy - 1, sx + 2, sy + 2);
226 cairo_set_source_rgb (cache_cr, 0, 0, 0);
227 cairo_fill(cache_cr);
229 cairo_pattern_t *pt = cairo_pattern_create_linear(ox, oy, ox, sy);
230 cairo_pattern_add_color_stop_rgb(pt, 0.0, 0.44, 0.44, 0.30);
231 cairo_pattern_add_color_stop_rgb(pt, 0.025, 0.89, 0.99, 0.54);
232 cairo_pattern_add_color_stop_rgb(pt, 0.4, 0.78, 0.89, 0.45);
233 cairo_pattern_add_color_stop_rgb(pt, 0.400001,0.71, 0.82, 0.33);
234 cairo_pattern_add_color_stop_rgb(pt, 1.0, 0.89, 1.00, 0.45);
235 cairo_set_source (cache_cr, pt);
236 cairo_rectangle(cache_cr, ox, oy, sx, sy);
237 cairo_fill(cache_cr);
239 cairo_select_font_face(cache_cr, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
240 cairo_set_font_size(cache_cr, 9);
242 cairo_impl cache_cimpl;
243 cache_cimpl.context = cache_cr;
245 lg->source->get_changed_offsets( lg->source_id, gen_index, cache_graph_index, cache_dot_index, cache_grid_index );
246 lg->last_generation = gen_index;
248 cairo_set_line_width(cache_cr, 1);
249 for(int phase = 1; phase <= 2; phase++)
251 for(grid_n = 0; legend = std::string(), cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.6), (grid_n<cache_grid_index) && lg->source->get_gridline(lg->source_id, grid_n, pos, vertical, legend, &cache_cimpl); grid_n++)
253 calf_line_graph_draw_grid( cache_cr, legend, vertical, pos, phase, sx, sy );
256 grid_n_save = grid_n;
258 gdk_cairo_set_source_color(cache_cr, &sc2);
259 cairo_set_line_join(cache_cr, CAIRO_LINE_JOIN_MITER);
260 cairo_set_line_width(cache_cr, 1);
261 for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, 2 * sx, &cache_cimpl); graph_n++)
263 calf_line_graph_draw_graph( cache_cr, data, sx, sy );
265 gdk_cairo_set_source_color(cache_cr, &sc3);
266 for(dot_n = 0; (dot_n<cache_dot_index) && lg->source->get_dot(lg->source_id, dot_n, x, y, size = 3, &cache_cimpl); dot_n++)
268 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
269 cairo_arc(cache_cr, ox + x * sx, yv, size, 0, 2 * M_PI);
270 cairo_fill(cache_cr);
273 // copy window to cache.
274 cairo_destroy( cache_cr );
275 calf_line_graph_copy_cache_to_window( lg, c );
276 } else {
277 grid_n_save = cache_grid_index;
278 graph_n = cache_graph_index;
279 dot_n = cache_dot_index;
280 calf_line_graph_copy_cache_to_window( lg, c );
283 cairo_rectangle(c, ox, oy, sx, sy);
284 cairo_clip(c);
286 cairo_set_line_width(c, 1);
287 for(int phase = 1; phase <= 2; phase++)
289 for(int gn=grid_n_save; legend = std::string(), cairo_set_source_rgba(c, 0, 0, 0, 0.6), lg->source->get_gridline(lg->source_id, gn, pos, vertical, legend, &cimpl); gn++)
291 calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
295 gdk_cairo_set_source_color(c, &sc2);
296 cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
297 cairo_set_line_width(c, 1);
298 for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
300 calf_line_graph_draw_graph( c, data, sx, sy );
302 gdk_cairo_set_source_color(c, &sc3);
303 for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
305 int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
306 cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
307 cairo_fill(c);
309 delete []data;
312 cairo_destroy(c);
314 // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
316 return TRUE;
319 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
321 g_assert(CALF_IS_LINE_GRAPH(graph));
322 graph->is_square = is_square;
325 int calf_line_graph_update_if(CalfLineGraph *graph, int last_drawn_generation)
327 g_assert(CALF_IS_LINE_GRAPH(graph));
328 int generation = last_drawn_generation;
329 if (graph->source)
331 int subgraph, dot, gridline;
332 generation = graph->source->get_changed_offsets(graph->source_id, generation, subgraph, dot, gridline);
333 if (subgraph == INT_MAX && dot == INT_MAX && gridline == INT_MAX && generation == last_drawn_generation)
334 return generation;
335 gtk_widget_queue_draw(GTK_WIDGET(graph));
337 return generation;
340 static void
341 calf_line_graph_size_request (GtkWidget *widget,
342 GtkRequisition *requisition)
344 g_assert(CALF_IS_LINE_GRAPH(widget));
346 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
349 static void
350 calf_line_graph_size_allocate (GtkWidget *widget,
351 GtkAllocation *allocation)
353 g_assert(CALF_IS_LINE_GRAPH(widget));
354 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
356 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
358 if( lg->cache_surface )
359 cairo_surface_destroy( lg->cache_surface );
360 lg->cache_surface = NULL;
362 widget->allocation = *allocation;
363 GtkAllocation &a = widget->allocation;
364 if (lg->is_square)
366 if (a.width > a.height)
368 a.x += (a.width - a.height) / 2;
369 a.width = a.height;
371 if (a.width < a.height)
373 a.y += (a.height - a.width) / 2;
374 a.height = a.width;
377 parent_class->size_allocate( widget, &a );
380 static void
381 calf_line_graph_class_init (CalfLineGraphClass *klass)
383 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
384 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
385 widget_class->expose_event = calf_line_graph_expose;
386 widget_class->size_request = calf_line_graph_size_request;
387 widget_class->size_allocate = calf_line_graph_size_allocate;
390 static void
391 calf_line_graph_init (CalfLineGraph *self)
393 GtkWidget *widget = GTK_WIDGET(self);
394 widget->requisition.width = 40;
395 widget->requisition.height = 40;
396 self->cache_surface = NULL;
397 self->last_generation = 0;
400 GtkWidget *
401 calf_line_graph_new()
403 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
406 GType
407 calf_line_graph_get_type (void)
409 static GType type = 0;
410 if (!type) {
411 static const GTypeInfo type_info = {
412 sizeof(CalfLineGraphClass),
413 NULL, /* base_init */
414 NULL, /* base_finalize */
415 (GClassInitFunc)calf_line_graph_class_init,
416 NULL, /* class_finalize */
417 NULL, /* class_data */
418 sizeof(CalfLineGraph),
419 0, /* n_preallocs */
420 (GInstanceInitFunc)calf_line_graph_init
423 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
425 for (int i = 0; ; i++) {
426 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
427 if (g_type_from_name(name)) {
428 free(name);
429 continue;
431 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
432 name,
433 type_info_copy,
434 (GTypeFlags)0);
435 free(name);
436 break;
439 return type;
442 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
444 static gboolean
445 calf_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
447 g_assert(CALF_IS_VUMETER(widget));
450 CalfVUMeter *vu = CALF_VUMETER(widget);
451 GtkStyle *style;
453 int ox = 4, oy = 3, led = 3, inner = 1, rad, pad;
454 int sx = widget->allocation.width - (ox * 2) - ((widget->allocation.width - inner * 2 - ox * 2) % led) - 1, sy = widget->allocation.height - (oy * 2);
455 style = gtk_widget_get_style(widget);
456 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
458 if( vu->cache_surface == NULL ) {
459 // looks like its either first call or the widget has been resized.
460 // create the cache_surface.
461 cairo_surface_t *window_surface = cairo_get_target( c );
462 vu->cache_surface = cairo_surface_create_similar( window_surface,
463 CAIRO_CONTENT_COLOR,
464 widget->allocation.width,
465 widget->allocation.height );
467 // And render the meterstuff again.
469 cairo_t *cache_cr = cairo_create( vu->cache_surface );
471 // theme background for reduced width and round borders
472 // if(widget->style->bg_pixmap[0] == NULL) {
473 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
474 // } else {
475 // gdk_cairo_set_source_pixbuf(cache_cr, GDK_PIXBUF(widget->style->bg_pixmap[0]), widget->allocation.x, widget->allocation.y + 20);
476 // }
477 cairo_paint(cache_cr);
479 // outer (light)
480 pad = 0;
481 rad = 6;
482 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
483 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
484 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
485 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
486 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
487 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
488 cairo_pattern_t *pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
489 cairo_pattern_add_color_stop_rgba (pat2, 0, 0, 0, 0, 0.3);
490 cairo_pattern_add_color_stop_rgba (pat2, 1, 1, 1, 1, 0.6);
491 cairo_set_source (cache_cr, pat2);
492 cairo_fill(cache_cr);
494 // inner (black)
495 pad = 1;
496 rad = 5;
497 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
498 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
499 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
500 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
501 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
502 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
503 pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
504 cairo_pattern_add_color_stop_rgba (pat2, 0, 0.23, 0.23, 0.23, 1);
505 cairo_pattern_add_color_stop_rgba (pat2, 0.5, 0, 0, 0, 1);
506 cairo_set_source (cache_cr, pat2);
507 //cairo_set_source_rgb(cache_cr, 0, 0, 0);
508 cairo_fill(cache_cr);
509 cairo_pattern_destroy(pat2);
511 cairo_rectangle(cache_cr, ox, oy, sx, sy);
512 cairo_set_source_rgb (cache_cr, 0, 0, 0);
513 cairo_fill(cache_cr);
515 cairo_set_line_width(cache_cr, 1);
517 for (int x = ox + inner; x <= ox + sx - led; x += led)
519 float ts = (x - ox) * 1.0 / sx;
520 float r = 0.f, g = 0.f, b = 0.f;
521 switch(vu->mode)
523 case VU_STANDARD:
524 default:
525 if (ts < 0.75)
526 r = ts / 0.75, g = 0.5 + ts * 0.66, b = 1 - ts / 0.75;
527 else
528 r = 1, g = 1 - (ts - 0.75) / 0.25, b = 0;
529 // if (vu->value < ts || vu->value <= 0)
530 // r *= 0.5, g *= 0.5, b *= 0.5;
531 break;
532 case VU_MONOCHROME_REVERSE:
533 r = 0, g = 170.0 / 255.0, b = 1;
534 // if (!(vu->value < ts) || vu->value >= 1.0)
535 // r *= 0.5, g *= 0.5, b *= 0.5;
536 break;
537 case VU_MONOCHROME:
538 r = 0, g = 170.0 / 255.0, b = 1;
539 // if (vu->value < ts || vu->value <= 0)
540 // r *= 0.5, g *= 0.5, b *= 0.5;
541 break;
543 GdkColor sc2 = { 0, (guint16)(65535 * r + 0.2), (guint16)(65535 * g), (guint16)(65535 * b) };
544 GdkColor sc3 = { 0, (guint16)(65535 * r * 0.7), (guint16)(65535 * g * 0.7), (guint16)(65535 * b * 0.7) };
545 gdk_cairo_set_source_color(cache_cr, &sc2);
546 cairo_move_to(cache_cr, x + 0.5, oy + 1);
547 cairo_line_to(cache_cr, x + 0.5, oy + sy - inner);
548 cairo_stroke(cache_cr);
549 gdk_cairo_set_source_color(cache_cr, &sc3);
550 cairo_move_to(cache_cr, x + 1.5, oy + sy - inner);
551 cairo_line_to(cache_cr, x + 1.5, oy + 1);
552 cairo_stroke(cache_cr);
554 // create blinder pattern
555 vu->pat = cairo_pattern_create_linear (ox, oy, ox, ox + sy);
556 cairo_pattern_add_color_stop_rgba (vu->pat, 0, 0.5, 0.5, 0.5, 0.6);
557 cairo_pattern_add_color_stop_rgba (vu->pat, 0.4, 0, 0, 0, 0.6);
558 cairo_pattern_add_color_stop_rgba (vu->pat, 0.401, 0, 0, 0, 0.8);
559 cairo_pattern_add_color_stop_rgba (vu->pat, 1, 0, 0, 0, 0.6);
560 cairo_destroy( cache_cr );
563 cairo_set_source_surface( c, vu->cache_surface, 0,0 );
564 cairo_paint( c );
565 cairo_set_source( c, vu->pat );
567 // get microseconds
568 timeval tv;
569 gettimeofday(&tv, 0);
570 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
572 // limit to 1.f
573 float value_orig = vu->value > 1.f ? 1.f : vu->value;
574 value_orig = value_orig < 0.f ? 0.f : value_orig;
575 float value = 0.f;
577 // falloff?
578 if(vu->vumeter_falloff > 0.f and vu->mode != VU_MONOCHROME_REVERSE) {
579 // fall off a bit
580 float s = ((float)(time - vu->last_falltime) / 1000000.0);
581 float m = vu->last_falloff * s * vu->vumeter_falloff;
582 vu->last_falloff -= m;
583 // new max value?
584 if(value_orig > vu->last_falloff) {
585 vu->last_falloff = value_orig;
587 value = vu->last_falloff;
588 vu->last_falltime = time;
589 vu->falling = vu->last_falloff > 0.000001;
590 } else {
591 // falloff disabled
592 vu->last_falloff = 0.f;
593 vu->last_falltime = 0.f;
594 value = value_orig;
597 if(vu->vumeter_hold > 0.0) {
598 // peak hold timer
599 if(time - (long)(vu->vumeter_hold * 1000 * 1000) > vu->last_hold) {
600 // time's up, reset
601 vu->last_value = value;
602 vu->last_hold = time;
603 vu->holding = false;
605 if( vu->mode == VU_MONOCHROME_REVERSE ) {
606 if(value < vu->last_value) {
607 // value is above peak hold
608 vu->last_value = value;
609 vu->last_hold = time;
610 vu->holding = true;
612 int hpx = round(vu->last_value * (sx - 2 * inner));
613 hpx = hpx + (1 - (hpx + inner) % led);
614 int vpx = round((1 - value) * (sx - 2 * inner));
615 vpx = vpx + (1 - (vpx + inner) % led);
616 int widthA = std::min(led + hpx, (sx - 2 * inner));
617 int widthB = std::min(std::max((sx - 2 * inner) - vpx - led - hpx, 0), (sx - 2 * inner));
618 cairo_rectangle( c, ox + inner, oy + inner, hpx, sy - 2 * inner);
619 cairo_rectangle( c, ox + inner + widthA, oy + inner, widthB, sy - 2 * inner);
620 } else {
621 if(value > vu->last_value) {
622 // value is above peak hold
623 vu->last_value = value;
624 vu->last_hold = time;
625 vu->holding = true;
627 int hpx = round((1 - vu->last_value) * (sx - 2 * inner));
628 hpx = hpx + (1 - (hpx + inner) % led);
629 int vpx = round(value * (sx - 2 * inner));
630 vpx = vpx + (1 - (vpx + inner) % led);
631 int width = std::min(std::max((sx - 2 * inner) - vpx - led - hpx, 0), (sx - 2 * inner));
632 cairo_rectangle( c, ox + inner + vpx, oy + inner, width, sy - 2 * inner);
633 cairo_rectangle( c, ox + inner + (sx - 2 * inner) - hpx, oy + inner, hpx, sy - 2 * inner);
635 } else {
636 // darken normally
637 if( vu->mode == VU_MONOCHROME_REVERSE )
638 cairo_rectangle( c, ox + inner,oy + inner, value * (sx - 2 * inner), sy - 2 * inner);
639 else
640 cairo_rectangle( c, ox + inner + value * (sx - 2 * inner), oy + inner, (sx - 2 * inner) * (1 - value), sy - 2 * inner );
642 cairo_fill( c );
643 cairo_destroy(c);
644 //gtk_paint_shadow(widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL, widget, NULL, ox - 2, oy - 2, sx + 4, sy + 4);
645 //printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
647 return TRUE;
650 static void
651 calf_vumeter_size_request (GtkWidget *widget,
652 GtkRequisition *requisition)
654 g_assert(CALF_IS_VUMETER(widget));
656 requisition->width = 50;
657 requisition->height = 18;
660 static void
661 calf_vumeter_size_allocate (GtkWidget *widget,
662 GtkAllocation *allocation)
664 g_assert(CALF_IS_VUMETER(widget));
665 CalfVUMeter *vu = CALF_VUMETER(widget);
667 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_VUMETER_GET_CLASS( vu ) );
669 parent_class->size_allocate( widget, allocation );
671 if( vu->cache_surface )
672 cairo_surface_destroy( vu->cache_surface );
673 vu->cache_surface = NULL;
676 static void
677 calf_vumeter_class_init (CalfVUMeterClass *klass)
679 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
680 widget_class->expose_event = calf_vumeter_expose;
681 widget_class->size_request = calf_vumeter_size_request;
682 widget_class->size_allocate = calf_vumeter_size_allocate;
685 static void
686 calf_vumeter_init (CalfVUMeter *self)
688 GtkWidget *widget = GTK_WIDGET(self);
689 //GTK_WIDGET_SET_FLAGS (widget, GTK_NO_WINDOW);
690 widget->requisition.width = 50;
691 widget->requisition.height = 18;
692 self->cache_surface = NULL;
693 self->falling = false;
694 self->holding = false;
697 GtkWidget *
698 calf_vumeter_new()
700 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER, NULL ));
703 GType
704 calf_vumeter_get_type (void)
706 static GType type = 0;
707 if (!type) {
708 static const GTypeInfo type_info = {
709 sizeof(CalfVUMeterClass),
710 NULL, /* base_init */
711 NULL, /* base_finalize */
712 (GClassInitFunc)calf_vumeter_class_init,
713 NULL, /* class_finalize */
714 NULL, /* class_data */
715 sizeof(CalfVUMeter),
716 0, /* n_preallocs */
717 (GInstanceInitFunc)calf_vumeter_init
720 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
722 for (int i = 0; ; i++) {
723 char *name = g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init) >> 16, i);
724 if (g_type_from_name(name)) {
725 free(name);
726 continue;
728 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
729 name,
730 type_info_copy,
731 (GTypeFlags)0);
732 free(name);
733 break;
736 return type;
739 extern void calf_vumeter_set_value(CalfVUMeter *meter, float value)
741 if (value != meter->value or meter->holding or meter->falling)
743 meter->value = value;
744 gtk_widget_queue_draw(GTK_WIDGET(meter));
748 extern float calf_vumeter_get_value(CalfVUMeter *meter)
750 return meter->value;
753 extern void calf_vumeter_set_mode(CalfVUMeter *meter, CalfVUMeterMode mode)
755 if (mode != meter->mode)
757 meter->mode = mode;
758 if(mode == VU_MONOCHROME_REVERSE) {
759 meter->value = 1.f;
760 meter->last_value = 1.f;
761 } else {
762 meter->value = 0.f;
763 meter->last_value = 0.f;
765 meter->vumeter_falloff = 0.f;
766 meter->last_falloff = (long)0;
767 meter->last_hold = (long)0;
768 gtk_widget_queue_draw(GTK_WIDGET(meter));
772 extern CalfVUMeterMode calf_vumeter_get_mode(CalfVUMeter *meter)
774 return meter->mode;
777 extern void calf_vumeter_set_falloff(CalfVUMeter *meter, float value)
779 if (value != meter->vumeter_falloff)
781 meter->vumeter_falloff = value;
782 gtk_widget_queue_draw(GTK_WIDGET(meter));
786 extern float calf_vumeter_get_falloff(CalfVUMeter *meter)
788 return meter->vumeter_falloff;
791 extern void calf_vumeter_set_hold(CalfVUMeter *meter, float value)
793 if (value != meter->vumeter_falloff)
795 meter->vumeter_hold = value;
796 gtk_widget_queue_draw(GTK_WIDGET(meter));
800 extern float calf_vumeter_get_hold(CalfVUMeter *meter)
802 return meter->vumeter_hold;
805 ///////////////////////////////////////// knob ///////////////////////////////////////////////
807 static gboolean
808 calf_knob_expose (GtkWidget *widget, GdkEventExpose *event)
810 g_assert(CALF_IS_KNOB(widget));
812 CalfKnob *self = CALF_KNOB(widget);
813 GdkWindow *window = widget->window;
814 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
816 // printf("adjustment = %p value = %f\n", adj, adj->value);
817 int ox = widget->allocation.x, oy = widget->allocation.y;
818 ox += (widget->allocation.width - self->knob_size * 20) / 2;
819 oy += (widget->allocation.height - self->knob_size * 20) / 2;
821 int phase = (int)((adj->value - adj->lower) * 64 / (adj->upper - adj->lower));
822 // skip middle phase except for true middle value
823 if (self->knob_type == 1 && phase == 32) {
824 double pt = (adj->value - adj->lower) * 2.0 / (adj->upper - adj->lower) - 1.0;
825 if (pt < 0)
826 phase = 31;
827 if (pt > 0)
828 phase = 33;
830 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
831 if (self->knob_type == 3 && !(phase % 16)) {
832 if (phase == 64)
833 phase = 0;
834 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
835 double diff = (adj->value - nom) / (adj->upper - adj->lower);
836 if (diff > 0.0001)
837 phase = (phase + 1) % 64;
838 if (diff < -0.0001)
839 phase = (phase + 63) % 64;
841 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget))->knob_image[self->knob_size - 1], phase * self->knob_size * 20, self->knob_type * self->knob_size * 20, ox, oy, self->knob_size * 20, self->knob_size * 20, GDK_RGB_DITHER_NORMAL, 0, 0);
842 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
843 if (gtk_widget_is_focus(widget))
845 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, self->knob_size * 20, self->knob_size * 20);
848 return TRUE;
851 static void
852 calf_knob_size_request (GtkWidget *widget,
853 GtkRequisition *requisition)
855 g_assert(CALF_IS_KNOB(widget));
857 CalfKnob *self = CALF_KNOB(widget);
859 // width/height is hardwired at 40px now
860 // is now chooseable by "size" value in XML (1-4)
861 requisition->width = 20 * self->knob_size;
862 requisition->height = 20 * self->knob_size;
865 static void
866 calf_knob_incr (GtkWidget *widget, int dir_down)
868 g_assert(CALF_IS_KNOB(widget));
869 CalfKnob *self = CALF_KNOB(widget);
870 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
872 int oldstep = (int)(0.5f + (adj->value - adj->lower) / adj->step_increment);
873 int step;
874 int nsteps = (int)(0.5f + (adj->upper - adj->lower) / adj->step_increment); // less 1 actually
875 if (dir_down)
876 step = oldstep - 1;
877 else
878 step = oldstep + 1;
879 if (self->knob_type == 3 && step >= nsteps)
880 step %= nsteps;
881 if (self->knob_type == 3 && step < 0)
882 step = nsteps - (nsteps - step) % nsteps;
884 // trying to reduce error cumulation here, by counting from lowest or from highest
885 float value = adj->lower + step * double(adj->upper - adj->lower) / nsteps;
886 gtk_range_set_value(GTK_RANGE(widget), value);
887 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
890 static gboolean
891 calf_knob_key_press (GtkWidget *widget, GdkEventKey *event)
893 g_assert(CALF_IS_KNOB(widget));
894 CalfKnob *self = CALF_KNOB(widget);
895 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
897 switch(event->keyval)
899 case GDK_Home:
900 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
901 return TRUE;
903 case GDK_End:
904 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
905 return TRUE;
907 case GDK_Up:
908 calf_knob_incr(widget, 0);
909 return TRUE;
911 case GDK_Down:
912 calf_knob_incr(widget, 1);
913 return TRUE;
915 case GDK_Shift_L:
916 case GDK_Shift_R:
917 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
918 self->start_y = self->last_y;
919 return TRUE;
922 return FALSE;
925 static gboolean
926 calf_knob_key_release (GtkWidget *widget, GdkEventKey *event)
928 g_assert(CALF_IS_KNOB(widget));
929 CalfKnob *self = CALF_KNOB(widget);
931 if(event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
933 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
934 self->start_y = self->last_y;
935 return TRUE;
938 return FALSE;
941 static gboolean
942 calf_knob_button_press (GtkWidget *widget, GdkEventButton *event)
944 g_assert(CALF_IS_KNOB(widget));
945 CalfKnob *self = CALF_KNOB(widget);
947 // CalfKnob *lg = CALF_KNOB(widget);
948 gtk_widget_grab_focus(widget);
949 gtk_grab_add(widget);
950 self->start_x = event->x;
951 self->start_y = event->y;
952 self->start_value = gtk_range_get_value(GTK_RANGE(widget));
954 return TRUE;
957 static gboolean
958 calf_knob_button_release (GtkWidget *widget, GdkEventButton *event)
960 g_assert(CALF_IS_KNOB(widget));
962 if (GTK_WIDGET_HAS_GRAB(widget))
963 gtk_grab_remove(widget);
964 return FALSE;
967 static inline float endless(float value)
969 if (value >= 0)
970 return fmod(value, 1.f);
971 else
972 return fmod(1.f - fmod(1.f - value, 1.f), 1.f);
975 static inline float deadzone(GtkWidget *widget, float value, float incr, float scale)
977 CalfKnob *self = CALF_KNOB(widget);
978 float dz = 20 / scale;
979 if(self->last_dz < 0.f) {
980 self -> last_dz = value + incr;
981 } else if (self->last_dz > 1.f) {
982 self->last_dz = 1.f;
983 } else {
984 self->last_dz += incr;
986 if(self->last_dz > 0.5 + dz) {
987 return std::min(0.5 + (self->last_dz - 0.5 - dz) * 0.5 / (0.5 - dz), (double)1);
989 if(self->last_dz < 0.5 - dz) {
990 return std::max(self->last_dz * 0.5 / (0.5 - dz), (double)0);
992 return 0.5;
995 static gboolean
996 calf_knob_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
998 g_assert(CALF_IS_KNOB(widget));
999 CalfKnob *self = CALF_KNOB(widget);
1001 float scale = (event->state & GDK_SHIFT_MASK) ? 1000 : 100;
1002 gboolean moved = FALSE;
1004 if (GTK_WIDGET_HAS_GRAB(widget))
1006 float sens = 1 + fabs(event->x - self->start_x) / 10;
1007 if (self->knob_type == 3)
1009 gtk_range_set_value(GTK_RANGE(widget), endless(gtk_range_get_value(GTK_RANGE(widget)) - (event->y - self->last_y) / (scale * sens)));
1011 else
1012 if (self->knob_type == 1)
1014 gtk_range_set_value(GTK_RANGE(widget), deadzone(GTK_WIDGET(widget), gtk_range_get_value(GTK_RANGE(widget)), -(event->y - self->last_y) / (scale * sens), (scale * sens)));
1016 else
1018 gtk_range_set_value(GTK_RANGE(widget), gtk_range_get_value(GTK_RANGE(widget)) - (event->y - self->last_y) / (scale * sens));
1020 moved = TRUE;
1022 self->last_y = event->y;
1023 self->last_x = event->x;
1024 return moved;
1027 static gboolean
1028 calf_knob_scroll (GtkWidget *widget, GdkEventScroll *event)
1030 calf_knob_incr(widget, event->direction);
1031 return TRUE;
1034 static void
1035 calf_knob_class_init (CalfKnobClass *klass)
1037 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1038 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1039 widget_class->expose_event = calf_knob_expose;
1040 widget_class->size_request = calf_knob_size_request;
1041 widget_class->button_press_event = calf_knob_button_press;
1042 widget_class->button_release_event = calf_knob_button_release;
1043 widget_class->motion_notify_event = calf_knob_pointer_motion;
1044 widget_class->key_press_event = calf_knob_key_press;
1045 widget_class->key_release_event = calf_knob_key_release;
1046 widget_class->scroll_event = calf_knob_scroll;
1047 GError *error = NULL;
1048 klass->knob_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob1.png", &error);
1049 klass->knob_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob2.png", &error);
1050 klass->knob_image[2] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob3.png", &error);
1051 klass->knob_image[3] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob4.png", &error);
1052 klass->knob_image[4] = gdk_pixbuf_new_from_file(PKGLIBDIR "/knob5.png", &error);
1053 g_assert(klass->knob_image != NULL);
1056 static void
1057 calf_knob_init (CalfKnob *self)
1059 GtkWidget *widget = GTK_WIDGET(self);
1060 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
1061 widget->requisition.width = 40;
1062 widget->requisition.height = 40;
1063 self->last_dz = -1.f;
1066 GtkWidget *
1067 calf_knob_new()
1069 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
1070 return calf_knob_new_with_adjustment(adj);
1073 static gboolean calf_knob_value_changed(gpointer obj)
1075 GtkWidget *widget = (GtkWidget *)obj;
1076 gtk_widget_queue_draw(widget);
1077 return FALSE;
1080 GtkWidget *calf_knob_new_with_adjustment(GtkAdjustment *_adjustment)
1082 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_KNOB, NULL ));
1083 if (widget) {
1084 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
1085 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_knob_value_changed), widget);
1087 return widget;
1090 GType
1091 calf_knob_get_type (void)
1093 static GType type = 0;
1094 if (!type) {
1096 static const GTypeInfo type_info = {
1097 sizeof(CalfKnobClass),
1098 NULL, /* base_init */
1099 NULL, /* base_finalize */
1100 (GClassInitFunc)calf_knob_class_init,
1101 NULL, /* class_finalize */
1102 NULL, /* class_data */
1103 sizeof(CalfKnob),
1104 0, /* n_preallocs */
1105 (GInstanceInitFunc)calf_knob_init
1108 for (int i = 0; ; i++) {
1109 char *name = g_strdup_printf("CalfKnob%u%d",
1110 ((unsigned int)(intptr_t)calf_knob_class_init) >> 16, i);
1111 if (g_type_from_name(name)) {
1112 free(name);
1113 continue;
1115 type = g_type_register_static(GTK_TYPE_RANGE,
1116 name,
1117 &type_info,
1118 (GTypeFlags)0);
1119 free(name);
1120 break;
1123 return type;
1126 ///////////////////////////////////////// toggle ///////////////////////////////////////////////
1128 static gboolean
1129 calf_toggle_expose (GtkWidget *widget, GdkEventExpose *event)
1131 g_assert(CALF_IS_TOGGLE(widget));
1133 CalfToggle *self = CALF_TOGGLE(widget);
1134 GdkWindow *window = widget->window;
1136 int ox = widget->allocation.x + widget->allocation.width / 2 - self->size * 15;
1137 int oy = widget->allocation.y + widget->allocation.height / 2 - self->size * 10;
1138 int width = self->size * 30, height = self->size * 20;
1140 gdk_draw_pixbuf(GDK_DRAWABLE(widget->window), widget->style->fg_gc[0], CALF_TOGGLE_CLASS(GTK_OBJECT_GET_CLASS(widget))->toggle_image[self->size - 1], 0, height * gtk_range_get_value(GTK_RANGE(widget)), ox, oy, width, height, GDK_RGB_DITHER_NORMAL, 0, 0);
1141 if (gtk_widget_is_focus(widget))
1143 gtk_paint_focus(widget->style, window, GTK_STATE_NORMAL, NULL, widget, NULL, ox, oy, width, height);
1146 return TRUE;
1149 static void
1150 calf_toggle_size_request (GtkWidget *widget,
1151 GtkRequisition *requisition)
1153 g_assert(CALF_IS_TOGGLE(widget));
1155 CalfToggle *self = CALF_TOGGLE(widget);
1157 requisition->width = 30 * self->size;
1158 requisition->height = 20 * self->size;
1161 static gboolean
1162 calf_toggle_button_press (GtkWidget *widget, GdkEventButton *event)
1164 g_assert(CALF_IS_TOGGLE(widget));
1165 GtkAdjustment *adj = gtk_range_get_adjustment(GTK_RANGE(widget));
1166 if (gtk_range_get_value(GTK_RANGE(widget)) == adj->lower)
1168 gtk_range_set_value(GTK_RANGE(widget), adj->upper);
1169 } else {
1170 gtk_range_set_value(GTK_RANGE(widget), adj->lower);
1172 return TRUE;
1175 static gboolean
1176 calf_toggle_key_press (GtkWidget *widget, GdkEventKey *event)
1178 switch(event->keyval)
1180 case GDK_Return:
1181 case GDK_KP_Enter:
1182 case GDK_space:
1183 return calf_toggle_button_press(widget, NULL);
1185 return FALSE;
1188 static void
1189 calf_toggle_class_init (CalfToggleClass *klass)
1191 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1192 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1193 widget_class->expose_event = calf_toggle_expose;
1194 widget_class->size_request = calf_toggle_size_request;
1195 widget_class->button_press_event = calf_toggle_button_press;
1196 widget_class->key_press_event = calf_toggle_key_press;
1197 GError *error = NULL;
1198 klass->toggle_image[0] = gdk_pixbuf_new_from_file(PKGLIBDIR "/toggle1.png", &error);
1199 klass->toggle_image[1] = gdk_pixbuf_new_from_file(PKGLIBDIR "/toggle2.png", &error);
1200 g_assert(klass->toggle_image != NULL);
1203 static void
1204 calf_toggle_init (CalfToggle *self)
1206 GtkWidget *widget = GTK_WIDGET(self);
1207 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
1208 widget->requisition.width = 30;
1209 widget->requisition.height = 20;
1210 self->size = 1;
1213 GtkWidget *
1214 calf_toggle_new()
1216 GtkAdjustment *adj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 1, 1, 0, 0);
1217 return calf_toggle_new_with_adjustment(adj);
1220 static gboolean calf_toggle_value_changed(gpointer obj)
1222 GtkWidget *widget = (GtkWidget *)obj;
1223 gtk_widget_queue_draw(widget);
1224 return FALSE;
1227 GtkWidget *calf_toggle_new_with_adjustment(GtkAdjustment *_adjustment)
1229 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TOGGLE, NULL ));
1230 if (widget) {
1231 gtk_range_set_adjustment(GTK_RANGE(widget), _adjustment);
1232 gtk_signal_connect(GTK_OBJECT(widget), "value-changed", G_CALLBACK(calf_toggle_value_changed), widget);
1234 return widget;
1237 GType
1238 calf_toggle_get_type (void)
1240 static GType type = 0;
1241 if (!type) {
1243 static const GTypeInfo type_info = {
1244 sizeof(CalfToggleClass),
1245 NULL, /* base_init */
1246 NULL, /* base_finalize */
1247 (GClassInitFunc)calf_toggle_class_init,
1248 NULL, /* class_finalize */
1249 NULL, /* class_data */
1250 sizeof(CalfToggle),
1251 0, /* n_preallocs */
1252 (GInstanceInitFunc)calf_toggle_init
1255 for (int i = 0; ; i++) {
1256 char *name = g_strdup_printf("CalfToggle%u%d",
1257 ((unsigned int)(intptr_t)calf_toggle_class_init) >> 16, i);
1258 if (g_type_from_name(name)) {
1259 free(name);
1260 continue;
1262 type = g_type_register_static( GTK_TYPE_RANGE,
1263 name,
1264 &type_info,
1265 (GTypeFlags)0);
1266 free(name);
1267 break;
1270 return type;
1273 ///////////////////////////////////////// tube ///////////////////////////////////////////////
1275 static gboolean
1276 calf_tube_expose (GtkWidget *widget, GdkEventExpose *event)
1278 g_assert(CALF_IS_TUBE(widget));
1280 CalfTube *self = CALF_TUBE(widget);
1281 GdkWindow *window = widget->window;
1282 GtkStyle *style = gtk_widget_get_style(widget);
1283 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(window));
1285 int ox = 4, oy = 4, inner = 1, rad, pad;
1286 int sx = widget->allocation.width - (ox * 2), sy = widget->allocation.height - (oy * 2);
1288 if( self->cache_surface == NULL ) {
1289 // looks like its either first call or the widget has been resized.
1290 // create the cache_surface.
1291 cairo_surface_t *window_surface = cairo_get_target( c );
1292 self->cache_surface = cairo_surface_create_similar( window_surface,
1293 CAIRO_CONTENT_COLOR,
1294 widget->allocation.width,
1295 widget->allocation.height );
1297 // And render the meterstuff again.
1298 cairo_t *cache_cr = cairo_create( self->cache_surface );
1299 // theme background for reduced width and round borders
1300 // if(widget->style->bg_pixmap[0] == NULL) {
1301 gdk_cairo_set_source_color(cache_cr,&style->bg[GTK_STATE_NORMAL]);
1302 // } else {
1303 // gdk_cairo_set_source_pixbuf(cache_cr, GDK_PIXBUF(widget->style->bg_pixmap[0]), widget->allocation.x, widget->allocation.y + 20);
1304 // }
1305 cairo_paint(cache_cr);
1307 // outer (light)
1308 pad = 0;
1309 rad = 6;
1310 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
1311 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
1312 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
1313 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
1314 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
1315 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
1316 cairo_pattern_t *pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
1317 cairo_pattern_add_color_stop_rgba (pat2, 0, 0, 0, 0, 0.3);
1318 cairo_pattern_add_color_stop_rgba (pat2, 1, 1, 1, 1, 0.6);
1319 cairo_set_source (cache_cr, pat2);
1320 cairo_fill(cache_cr);
1322 // inner (black)
1323 pad = 1;
1324 rad = 5;
1325 cairo_arc(cache_cr, rad + pad, rad + pad, rad, 0, 2 * M_PI);
1326 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, rad + pad, rad, 0, 2 * M_PI);
1327 cairo_arc(cache_cr, rad + pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
1328 cairo_arc(cache_cr, ox * 2 + sx - rad - pad, oy * 2 + sy - rad - pad, rad, 0, 2 * M_PI);
1329 cairo_rectangle(cache_cr, pad, rad + pad, sx + ox * 2 - pad * 2, sy + oy * 2 - rad * 2 - pad * 2);
1330 cairo_rectangle(cache_cr, rad + pad, pad, sx + ox * 2 - rad * 2 - pad * 2, sy + oy * 2 - pad * 2);
1331 pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
1332 cairo_pattern_add_color_stop_rgba (pat2, 0, 0.23, 0.23, 0.23, 1);
1333 cairo_pattern_add_color_stop_rgba (pat2, 0.5, 0, 0, 0, 1);
1334 cairo_set_source (cache_cr, pat2);
1335 //cairo_set_source_rgb(cache_cr, 0, 0, 0);
1336 cairo_fill(cache_cr);
1337 cairo_pattern_destroy(pat2);
1339 cairo_rectangle(cache_cr, ox, oy, sx, sy);
1340 cairo_set_source_rgb (cache_cr, 0, 0, 0);
1341 cairo_fill(cache_cr);
1343 cairo_surface_t *image;
1344 switch(self->direction) {
1345 case 1:
1346 // vertical
1347 switch(self->size) {
1348 default:
1349 case 1:
1350 image = cairo_image_surface_create_from_png (PKGLIBDIR "tubeV1.png");
1351 break;
1352 case 2:
1353 image = cairo_image_surface_create_from_png (PKGLIBDIR "tubeV2.png");
1354 break;
1356 break;
1357 default:
1358 case 2:
1359 // horizontal
1360 switch(self->size) {
1361 default:
1362 case 1:
1363 image = cairo_image_surface_create_from_png (PKGLIBDIR "tubeH1.png");
1364 break;
1365 case 2:
1366 image = cairo_image_surface_create_from_png (PKGLIBDIR "tubeH2.png");
1367 break;
1369 break;
1371 cairo_set_source_surface (cache_cr, image, widget->allocation.width / 2 - sx / 2 + inner, widget->allocation.height / 2 - sy / 2 + inner);
1372 cairo_paint (cache_cr);
1373 cairo_surface_destroy (image);
1374 cairo_destroy( cache_cr );
1377 cairo_set_source_surface( c, self->cache_surface, 0,0 );
1378 cairo_paint( c );
1380 // get microseconds
1381 timeval tv;
1382 gettimeofday(&tv, 0);
1383 long time = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
1385 // limit to 1.f
1386 float value_orig = self->value > 1.f ? 1.f : self->value;
1387 value_orig = value_orig < 0.f ? 0.f : value_orig;
1388 float value = 0.f;
1390 float s = ((float)(time - self->last_falltime) / 1000000.0);
1391 float m = self->last_falloff * s * 2.5;
1392 self->last_falloff -= m;
1393 // new max value?
1394 if(value_orig > self->last_falloff) {
1395 self->last_falloff = value_orig;
1397 value = self->last_falloff;
1398 self->last_falltime = time;
1399 self->falling = self->last_falloff > 0.000001;
1400 cairo_pattern_t *pat;
1401 // draw upper light
1402 switch(self->direction) {
1403 case 1:
1404 // vertical
1405 cairo_arc(c, ox + sx * 0.5, oy + sy * 0.2, sx, 0, 2 * M_PI);
1406 pat = cairo_pattern_create_radial (ox + sx * 0.5, oy + sy * 0.2, 3, ox + sx * 0.5, oy + sy * 0.2, sx);
1407 break;
1408 default:
1409 case 2:
1410 // horizontal
1411 cairo_arc(c, ox + sx * 0.8, oy + sy * 0.5, sy, 0, 2 * M_PI);
1412 pat = cairo_pattern_create_radial (ox + sx * 0.8, oy + sy * 0.5, 3, ox + sx * 0.8, oy + sy * 0.5, sy);
1413 break;
1415 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, value);
1416 cairo_pattern_add_color_stop_rgba (pat, 0.3, 1, 0.8, 0.3, value * 0.4);
1417 cairo_pattern_add_color_stop_rgba (pat, 0.31, 0.9, 0.5, 0.1, value * 0.5);
1418 cairo_pattern_add_color_stop_rgba (pat, 1, 0.0, 0.2, 0.7, 0);
1419 cairo_set_source (c, pat);
1420 cairo_fill(c);
1421 // draw lower light
1422 switch(self->direction) {
1423 case 1:
1424 // vertical
1425 cairo_arc(c, ox + sx * 0.5, oy + sy * 0.75, sx / 2, 0, 2 * M_PI);
1426 pat = cairo_pattern_create_radial (ox + sx * 0.5, oy + sy * 0.75, 2, ox + sx * 0.5, oy + sy * 0.75, sx / 2);
1427 break;
1428 default:
1429 case 2:
1430 // horizontal
1431 cairo_arc(c, ox + sx * 0.25, oy + sy * 0.5, sy / 2, 0, 2 * M_PI);
1432 pat = cairo_pattern_create_radial (ox + sx * 0.25, oy + sy * 0.5, 2, ox + sx * 0.25, oy + sy * 0.5, sy / 2);
1433 break;
1435 cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, value);
1436 cairo_pattern_add_color_stop_rgba (pat, 0.3, 1, 0.8, 0.3, value * 0.4);
1437 cairo_pattern_add_color_stop_rgba (pat, 0.31, 0.9, 0.5, 0.1, value * 0.5);
1438 cairo_pattern_add_color_stop_rgba (pat, 1, 0.0, 0.2, 0.7, 0);
1439 cairo_set_source (c, pat);
1440 cairo_fill(c);
1441 cairo_destroy(c);
1442 return TRUE;
1445 static void
1446 calf_tube_size_request (GtkWidget *widget,
1447 GtkRequisition *requisition)
1449 g_assert(CALF_IS_TUBE(widget));
1451 CalfTube *self = CALF_TUBE(widget);
1452 switch(self->direction) {
1453 case 1:
1454 switch(self->size) {
1455 case 1:
1456 widget->requisition.width = 82;
1457 widget->requisition.height = 130;
1458 break;
1459 default:
1460 case 2:
1461 widget->requisition.width = 130;
1462 widget->requisition.height = 210;
1463 break;
1465 break;
1466 default:
1467 case 2:
1468 switch(self->size) {
1469 case 1:
1470 widget->requisition.width = 130;
1471 widget->requisition.height = 82;
1472 break;
1473 default:
1474 case 2:
1475 widget->requisition.width = 210;
1476 widget->requisition.height = 130;
1477 break;
1479 break;
1483 static void
1484 calf_tube_size_allocate (GtkWidget *widget,
1485 GtkAllocation *allocation)
1487 g_assert(CALF_IS_TUBE(widget));
1488 CalfTube *tube = CALF_TUBE(widget);
1490 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_TUBE_GET_CLASS( tube ) );
1492 parent_class->size_allocate( widget, allocation );
1494 if( tube->cache_surface )
1495 cairo_surface_destroy( tube->cache_surface );
1496 tube->cache_surface = NULL;
1499 static void
1500 calf_tube_class_init (CalfTubeClass *klass)
1502 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1503 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1504 widget_class->expose_event = calf_tube_expose;
1505 widget_class->size_request = calf_tube_size_request;
1506 widget_class->size_allocate = calf_tube_size_allocate;
1509 static void
1510 calf_tube_init (CalfTube *self)
1512 GtkWidget *widget = GTK_WIDGET(self);
1513 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self), GTK_CAN_FOCUS);
1514 switch(self->direction) {
1515 case 1:
1516 switch(self->size) {
1517 case 1:
1518 widget->requisition.width = 82;
1519 widget->requisition.height = 130;
1520 break;
1521 default:
1522 case 2:
1523 widget->requisition.width = 130;
1524 widget->requisition.height = 210;
1525 break;
1527 break;
1528 default:
1529 case 2:
1530 switch(self->size) {
1531 case 1:
1532 widget->requisition.width = 130;
1533 widget->requisition.height = 82;
1534 break;
1535 default:
1536 case 2:
1537 widget->requisition.width = 210;
1538 widget->requisition.height = 130;
1539 break;
1541 break;
1543 self->falling = false;
1544 self->cache_surface = NULL;
1547 GtkWidget *
1548 calf_tube_new()
1550 GtkWidget *widget = GTK_WIDGET( g_object_new (CALF_TYPE_TUBE, NULL ));
1551 return widget;
1554 extern void calf_tube_set_value(CalfTube *tube, float value)
1556 if (value != tube->value or tube->falling)
1558 tube->value = value;
1559 gtk_widget_queue_draw(GTK_WIDGET(tube));
1563 GType
1564 calf_tube_get_type (void)
1566 static GType type = 0;
1567 if (!type) {
1569 static const GTypeInfo type_info = {
1570 sizeof(CalfTubeClass),
1571 NULL, /* base_init */
1572 NULL, /* base_finalize */
1573 (GClassInitFunc)calf_tube_class_init,
1574 NULL, /* class_finalize */
1575 NULL, /* class_data */
1576 sizeof(CalfTube),
1577 0, /* n_preallocs */
1578 (GInstanceInitFunc)calf_tube_init
1581 for (int i = 0; ; i++) {
1582 char *name = g_strdup_printf("CalfTube%u%d",
1583 ((unsigned int)(intptr_t)calf_tube_class_init) >> 16, i);
1584 if (g_type_from_name(name)) {
1585 free(name);
1586 continue;
1588 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
1589 name,
1590 &type_info,
1591 (GTypeFlags)0);
1592 free(name);
1593 break;
1596 return type;