Transient Designer: image and text for manual, small change to the UI
[calf.git] / src / ctl_linegraph.cpp
blobbfbd3667ba9ab33b15793f213d14b51a4f9bed87
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
22 #include "config.h"
23 #include <calf/custom_ctl.h>
24 #include <calf/ctl_linegraph.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <math.h>
27 #include <gdk/gdk.h>
28 #include <sys/time.h>
29 #include <iostream>
30 #include <calf/giface.h>
31 #include <stdint.h>
33 #define RGBAtoINT(r, g, b, a) ((uint32_t)(r * 255) << 24) + ((uint32_t)(g * 255) << 16) + ((uint32_t)(b * 255) << 8) + (uint32_t)(a * 255)
34 #define INTtoR(color) (float)((color & 0xff000000) >> 24) / 255.f
35 #define INTtoG(color) (float)((color & 0x00ff0000) >> 16) / 255.f
36 #define INTtoB(color) (float)((color & 0x0000ff00) >> 8) / 255.f
37 #define INTtoA(color) (float)((color & 0x000000ff) >> 0) / 255.f
39 using namespace calf_plugins;
41 static void
42 calf_line_graph_draw_grid( CalfLineGraph* lg, cairo_t *ctx, std::string &legend, bool vertical, float pos )
44 int sx = lg->size_x;
45 int sy = lg->size_y;
46 int ox = lg->pad_x;
47 int oy = lg->pad_y;
49 float x = 0, y = 0;
51 cairo_text_extents_t tx;
52 int size;
53 if (!legend.empty()) {
54 cairo_text_extents(ctx, legend.c_str(), &tx);
55 size = vertical ? tx.height : tx.width;
56 size += 5;
57 } else {
58 size = 0;
61 if (vertical)
63 x = floor(ox + pos * sx) + 0.5;
64 cairo_move_to(ctx, x, oy);
65 cairo_line_to(ctx, x, oy + sy - size);
66 cairo_stroke(ctx);
67 if (!legend.empty()) {
68 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
69 cairo_move_to(ctx, x - (tx.x_bearing + tx.width / 2.0), oy + sy - 2);
70 cairo_show_text(ctx, legend.c_str());
73 else
75 y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
76 cairo_move_to(ctx, ox, y);
77 cairo_line_to(ctx, ox + sx - size, y);
78 cairo_stroke(ctx);
80 if (!legend.empty()) {
81 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
82 cairo_move_to(ctx, ox + sx - 4 - tx.width, y + tx.height/2 - 2);
83 cairo_show_text(ctx, legend.c_str());
86 if (lg->debug > 2) printf("* grid vert: %d, x: %d, y: %d, label: %s\n", vertical ? 1 : 0, (int)x, (int)y, legend.c_str());
89 static int
90 calf_line_graph_draw_graph( CalfLineGraph* lg, cairo_t *ctx, float *data, int mode = 0 )
92 if (lg->debug) printf("(draw graph)\n");
94 int sx = lg->size_x;
95 int sy = lg->size_y;
96 int ox = lg->pad_x;
97 int oy = lg->pad_y;
99 int _lastx = 0;
100 float y = 0.f;
101 int startdraw = -1;
103 for (int i = 0; i < sx; i++) {
104 y = (oy + sy / 2 - (sy / 2 - 1) * data[i]);
105 if (lg->debug > 2) printf("* graph x: %d, y: %.5f, data: %.5f\n", i, y, data[i]);
106 switch (mode) {
107 case 0:
108 case 1:
109 default:
110 // we want to draw a line
111 if (i and (data[i] < INFINITY or i == sx - 1)) {
112 cairo_line_to(ctx, ox + i, y);
113 } else if (i and startdraw >= 0) {
114 continue;
115 } else {
116 cairo_move_to(ctx, ox, y);
117 if (startdraw < 0)
118 startdraw = i;
120 break;
121 case 2:
122 // bars are used
123 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
124 cairo_rectangle(ctx, ox + _lastx, (int)y, i - _lastx, sy - (int)y + oy);
125 _lastx = i;
126 if (startdraw < 0)
127 startdraw = ox + _lastx;
128 } else {
129 continue;
131 break;
132 case 3:
133 // this one is drawing little boxes at the values position
134 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
135 cairo_rectangle(ctx, ox + _lastx, (int)y - 1, i - _lastx, 2);
136 _lastx = i;
137 if (startdraw < 0)
138 startdraw = ox + _lastx;
139 } else {
140 continue;
142 break;
143 case 4:
144 // this one is drawing bars centered on the x axis
145 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
146 cairo_rectangle(ctx, ox + _lastx, oy + sy / 2, i - _lastx, -1 * data[i] * (sy / 2));
147 _lastx = i;
148 if (startdraw < 0)
149 startdraw = ox + _lastx;
150 } else {
151 continue;
153 break;
154 case 5:
155 // this one is drawing bars centered on the x axis with 1
156 // as the center
157 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
158 cairo_rectangle(ctx, ox + _lastx,oy + sy / 2 - sy * lg->offset / 2, i - _lastx, -1 * data[i] * (sy / 2) + sy * lg->offset / 2);
159 _lastx = i;
160 if (startdraw < 0)
161 startdraw = ox + _lastx;
162 } else {
163 continue;
165 break;
168 if (mode == 1) {
169 cairo_line_to(ctx, sx + 2 * ox, sy + 2 * oy);
170 cairo_line_to(ctx, 0, sy + 2 * oy);
171 cairo_close_path(ctx);
173 if(!mode) {
174 cairo_stroke(ctx);
175 } else {
176 cairo_fill(ctx);
178 return startdraw;
181 static void
182 calf_line_graph_draw_moving(CalfLineGraph* lg, cairo_t *ctx, float *data, int direction, int offset, int color)
184 if (lg->debug) printf("(draw moving)\n");
186 int sx = lg->size_x;
187 int sy = lg->size_y;
188 int ox = lg->pad_x;
189 int oy = lg->pad_y;
191 int _last = 0;
192 int startdraw = -1;
194 int sm = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? sx : sy);
195 int om = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? ox : oy);
196 for (int i = 0; i < sm; i++) {
197 if (lg->debug > 2) printf("* moving i: %d, dir: %d, offset: %d, data: %.5f\n", i, direction, offset, data[i]);
198 if (i and ((data[i] < INFINITY) or i >= sm)) {
199 cairo_set_source_rgba(ctx, INTtoR(color), INTtoG(color), INTtoB(color), (data[i] + 1) / 1.4 * INTtoA(color));
200 switch (direction) {
201 case LG_MOVING_LEFT:
202 default:
203 cairo_rectangle(ctx, ox + sx - 1 - offset, oy + _last, 1, i - _last);
204 break;
205 case LG_MOVING_RIGHT:
206 cairo_rectangle(ctx, ox + offset, oy + _last, 1, i - _last);
207 break;
208 case LG_MOVING_UP:
209 cairo_rectangle(ctx, ox + _last, oy + sy - 1 - offset, i - _last, 1);
210 break;
211 case LG_MOVING_DOWN:
212 cairo_rectangle(ctx, ox + _last, oy + offset, i - _last, 1);
213 break;
216 cairo_fill(ctx);
217 _last = i;
218 if (startdraw < 0)
219 startdraw = om + _last;
221 else
222 continue;
227 void calf_line_graph_draw_crosshairs(CalfLineGraph* lg, cairo_t* cache_cr, bool gradient, int gradient_rad, float alpha, int mask, bool circle, int x, int y, std::string label)
229 if (lg->debug) printf("(draw crosshairs)\n");
230 // crosshairs
232 int sx = lg->size_x;
233 int sy = lg->size_y;
234 int ox = lg->pad_x;
235 int oy = lg->pad_y;
237 int _x = ox + x;
238 int _y = ox + y;
240 cairo_pattern_t *pat;
242 if(mask > 0 and circle) {
243 //// draw a circle in the center of the crosshair leaving out
244 //// the lines
245 //// ne
246 //cairo_move_to(cache_cr, _x + 1, _y);
247 //cairo_arc (cache_cr, _x + 1, _y, mask, 1.5 * M_PI, 2 * M_PI);
248 //cairo_close_path(cache_cr);
249 //// se
250 //cairo_move_to(cache_cr, _x + 1, _y + 1);
251 //cairo_arc (cache_cr, _x + 1, _y + 1, mask, 0, 0.5 * M_PI);
252 //cairo_close_path(cache_cr);
253 //// sw
254 //cairo_move_to(cache_cr, _x, _y + 1);
255 //cairo_arc (cache_cr, _x, _y + 1, mask, 0.5 * M_PI, M_PI);
256 //cairo_close_path(cache_cr);
257 //// nw
258 //cairo_move_to(cache_cr, _x, _y);
259 //cairo_arc (cache_cr, _x, _y, mask, M_PI, 1.5 * M_PI);
260 //cairo_close_path(cache_cr);
261 cairo_move_to(cache_cr, _x, _y);
262 cairo_arc (cache_cr, _x, _y, mask, 0, 2 * M_PI);
263 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
264 cairo_fill(cache_cr);
265 if (alpha < 0.3) {
266 cairo_move_to(cache_cr, _x, _y);
267 cairo_arc (cache_cr, _x, _y, HANDLE_WIDTH / 2, 0, 2 * M_PI);
268 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.2);
269 cairo_fill(cache_cr);
272 if(gradient and gradient_rad > 0) {
273 // draw the crosshairs with a steady gradient around
274 pat = cairo_pattern_create_radial(_x, _y, 1, _x, _y, gradient_rad * 2);
275 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
276 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
277 // top
278 cairo_rectangle(cache_cr, _x, _y - gradient_rad, 1, gradient_rad - mask);
279 // right
280 cairo_rectangle(cache_cr, _x + mask, _y, gradient_rad - mask, 1);
281 // bottom
282 cairo_rectangle(cache_cr, _x, _y + mask, 1, gradient_rad - mask);
283 // left
284 cairo_rectangle(cache_cr, _x - gradient_rad, _y, gradient_rad - mask, 1);
286 cairo_set_source(cache_cr, pat);
287 cairo_fill(cache_cr);
288 } else if(gradient) {
289 // draw the crosshairs with a gradient to the frame
290 // top
291 cairo_rectangle(cache_cr, _x, oy, 1, y - mask);
292 pat = cairo_pattern_create_linear(_x, oy, _x, _y);
293 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
294 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
295 cairo_set_source(cache_cr, pat);
296 cairo_fill(cache_cr);
297 // right
298 cairo_rectangle(cache_cr, _x + mask, _y, sx - x - mask, 1);
299 pat = cairo_pattern_create_linear(_x, oy, sx, oy);
300 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
301 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
302 cairo_set_source(cache_cr, pat);
303 cairo_fill(cache_cr);
304 // bottom
305 cairo_rectangle(cache_cr, _x, _y + mask, 1, sy - y - mask);
306 pat = cairo_pattern_create_linear(_x, _y, _x, oy + sy);
307 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
308 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
309 cairo_set_source(cache_cr, pat);
310 cairo_fill(cache_cr);
311 // left
312 cairo_rectangle(cache_cr, ox, _y, x - mask, 1);
313 pat = cairo_pattern_create_linear(ox, oy, _x, oy);
314 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
315 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
316 cairo_set_source(cache_cr, pat);
317 cairo_fill(cache_cr);
318 } else {
319 // draw normal crosshairs
320 // top
321 cairo_move_to(cache_cr, _x + 0.5, oy + 0.5);
322 cairo_line_to(cache_cr, _x + 0.5, _y - mask + 0.5);
323 // right
324 cairo_move_to(cache_cr, _x + mask + 0.5, _y + 0.5);
325 cairo_line_to(cache_cr, ox + sx + 0.5, _y + 0.5);
326 // bottom
327 cairo_move_to(cache_cr, _x + 0.5, _y + mask + 0.5);
328 cairo_line_to(cache_cr, _x + 0.5, oy + sy + 0.5);
329 // left
330 cairo_move_to(cache_cr, ox + 0.5, _y + 0.5);
331 cairo_line_to(cache_cr, _x - mask + 0.5, _y + 0.5);
333 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
334 cairo_stroke(cache_cr);
336 if(label != "") {
337 // draw label
338 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.5);
339 cairo_move_to(cache_cr, lg->mouse_x + 3, lg->mouse_y - 3);
340 cairo_show_text(cache_cr, label.c_str());
341 cairo_fill(cache_cr);
346 void calf_line_graph_draw_freqhandles(CalfLineGraph* lg, cairo_t* c)
348 // freq_handles
349 if (lg->debug) printf("(draw handles)\n");
351 int sx = lg->size_x;
352 int sy = lg->size_y;
353 int ox = lg->pad_x;
354 int oy = lg->pad_y;
356 if (lg->freqhandles > 0) {
357 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 1.0);
358 cairo_set_line_width(c, 1.0);
360 for (int i = 0; i < lg->freqhandles; i++) {
361 FreqHandle *handle = &lg->freq_handles[i];
362 if(!handle->is_active())
363 continue;
366 if (handle->value_x > 0.0 && handle->value_x < 1.0) {
367 int val_x = round(handle->value_x * sx);
368 int val_y = (handle->dimensions >= 2) ? round(handle->value_y * sy) : 0;
369 float pat_alpha;
370 bool grad;
371 // choose colors between dragged and normal state
372 if (lg->handle_hovered == i) {
373 pat_alpha = 0.3;
374 grad = false;
375 cairo_set_source_rgba(c, 0, 0, 0, 0.7);
376 } else {
377 pat_alpha = 0.1;
378 grad = true;
379 //cairo_set_source_rgb(c, 0.44, 0.5, 0.21);
380 cairo_set_source_rgba(c, 0, 0, 0, 0.5);
382 if (handle->dimensions >= 2) {
383 cairo_move_to(c, val_x + 8, val_y);
384 } else {
385 cairo_move_to(c, val_x + 11, oy + 15);
387 // draw the freq label
388 float freq = exp((handle->value_x) * log(1000)) * 20.0;
389 std::stringstream ss;
390 ss << round(freq) << " Hz";
391 cairo_show_text(c, ss.str().c_str());
393 // draw the label on top
394 if (handle->label && handle->label[0]) {
396 cairo_select_font_face(c, "Sans",
397 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
398 cairo_set_font_size(c, 9);
399 cairo_text_extents_t te;
401 cairo_text_extents(c, handle->label, &te);
402 if (handle->dimensions >= 2) {
403 cairo_move_to(c, val_x - te.width, val_y);
404 } else {
405 cairo_move_to(c, val_x - 3 - te.width, oy + 15);
407 cairo_show_text(c, handle->label);
409 cairo_stroke(c);
411 if (handle->dimensions == 1) {
412 // draw the main line
413 cairo_move_to(c, ox + val_x + 0.5, oy);
414 cairo_line_to(c, ox + val_x + 0.5, oy + sy);
415 cairo_stroke(c);
416 // draw some one-dimensional bling-bling
417 cairo_pattern_t *pat;
418 switch(handle->style) {
419 default:
420 case 0:
421 // bell filters, default
422 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
423 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
424 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha);
425 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
426 cairo_rectangle(c, ox + val_x - 7, oy, 6, sy);
427 cairo_rectangle(c, ox + val_x + 2, oy, 6, sy);
428 break;
429 case 1:
430 // hipass
431 pat = cairo_pattern_create_linear(ox, oy, ox + val_x, oy);
432 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
433 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, pat_alpha);
434 cairo_rectangle(c, ox, oy, val_x - 1, sy);
435 break;
436 case 2:
437 // loshelf
438 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
439 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
440 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
441 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
442 cairo_rectangle(c, ox, oy, val_x - 1, sy);
443 break;
444 case 3:
445 // hishelf
446 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
447 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
448 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
449 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
450 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 2, sy);
451 break;
452 case 4:
453 // lopass
454 pat = cairo_pattern_create_linear(ox + val_x, oy, ox + sx, oy);
455 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, pat_alpha);
456 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
457 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 1, sy);
458 break;
460 cairo_set_source(c, pat);
461 cairo_fill(c);
462 cairo_pattern_destroy(pat);
463 } else {
464 int mask = 30 - log10(1 + handle->value_z * 9) * 30 + HANDLE_WIDTH / 2.f;
465 // (CalfLineGraph* lg, cairo_t* c, bool gradient, int gradient_rad, float alpha, int mask, bool circle, int x, int y, std::string label, int ox, int oy, int sx, int sy)
466 std::string s = "";
467 calf_line_graph_draw_crosshairs(lg, c, grad, -1, pat_alpha, mask, true, val_x, val_y, s);
475 static void
476 calf_line_graph_destroy_surfaces (GtkWidget *widget)
478 g_assert(CALF_IS_LINE_GRAPH(widget));
479 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
481 if (lg->debug) printf("{destroy surfaces}\n");
483 // destroy all surfaces - and don't tell anybody about it - hehe
484 if( lg->background_surface )
485 cairo_surface_destroy( lg->background_surface );
486 if( lg->grid_surface )
487 cairo_surface_destroy( lg->grid_surface );
488 if( lg->cache_surface )
489 cairo_surface_destroy( lg->cache_surface );
490 if( lg->moving_surface[0] )
491 cairo_surface_destroy( lg->moving_surface[0] );
492 if( lg->moving_surface[1] )
493 cairo_surface_destroy( lg->moving_surface[1] );
494 if( lg->handles_surface )
495 cairo_surface_destroy( lg->handles_surface );
496 if( lg->realtime_surface )
497 cairo_surface_destroy( lg->realtime_surface );
499 static void
500 calf_line_graph_create_surfaces (GtkWidget *widget)
502 g_assert(CALF_IS_LINE_GRAPH(widget));
503 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
505 if (lg->debug) printf("{create surfaces}\n");
507 int width = widget->allocation.width;
508 int height = widget->allocation.height;
510 // the size of the "real" drawing area
511 lg->size_x = width - lg->pad_x * 2;
512 lg->size_y = height - lg->pad_y * 2;
514 calf_line_graph_destroy_surfaces(widget);
515 // create the background surface.
516 // background holds the graphics of the frame and the yellowish
517 // background light for faster redrawing of static stuff
518 lg->background_surface = cairo_image_surface_create(
519 CAIRO_FORMAT_ARGB32, width, height );
521 // create the grid surface.
522 // this one is used as a cache for the grid on the background in the
523 // cache phase. If a graph or dot in cache phase needs to be redrawn
524 // we don't need to redraw the whole grid.
525 lg->grid_surface = cairo_image_surface_create(
526 CAIRO_FORMAT_ARGB32, width, height );
528 // create the cache surface.
529 // cache holds a copy of the background with a static part of
530 // the grid and some static curves and dots.
531 lg->cache_surface = cairo_image_surface_create(
532 CAIRO_FORMAT_ARGB32, width, height );
534 // create the moving surface.
535 // moving is used as a cache for any slowly moving graphics like
536 // spectralizer or waveforms
537 lg->moving_surface[0] = cairo_image_surface_create(
538 CAIRO_FORMAT_ARGB32, width, height );
540 // create the moving temp surface.
541 // moving is used as a cache for any slowly moving graphics like
542 // spectralizer or waveforms
543 lg->moving_surface[1] = cairo_image_surface_create(
544 CAIRO_FORMAT_ARGB32, width, height );
546 // create the handles surface.
547 // this one contains the handles graphics to avoid redrawing
548 // each cycle
549 lg->handles_surface = cairo_image_surface_create(
550 CAIRO_FORMAT_ARGB32, width, height );
552 // create the realtime surface.
553 // realtime is used to cache the realtime graphics for drawing the
554 // crosshairs on top if nothing else changed
555 lg->realtime_surface = cairo_image_surface_create(
556 CAIRO_FORMAT_ARGB32, width, height );
558 lg->force_cache = true;
561 static cairo_t
562 *calf_line_graph_switch_context(CalfLineGraph* lg, cairo_t *ctx, cairo_impl *cimpl)
564 if (lg->debug) printf("{switch context}\n");
566 cimpl->context = ctx;
567 cairo_select_font_face(ctx, "Sans",
568 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
569 cairo_set_font_size(ctx, 9);
570 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER);
571 cairo_rectangle(ctx, lg->pad_x, lg->pad_y, lg->size_x, lg->size_y);
572 cairo_clip(ctx);
573 return ctx;
576 static void
577 calf_line_graph_copy_surface(cairo_t *ctx, cairo_surface_t *source, float fade = 1.f)
579 // copy a surface to a cairo context
580 cairo_save(ctx);
581 cairo_set_source_surface(ctx, source, 0, 0);
582 if (fade < 1.0) {
583 cairo_paint_with_alpha(ctx, fade * 0.35 + 0.05);
584 } else {
585 cairo_paint(ctx);
587 cairo_restore(ctx);
590 static void
591 calf_line_graph_clear_surface(cairo_t *ctx)
593 // clears a surface to transparent
594 cairo_save (ctx);
595 cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
596 cairo_paint (ctx);
597 cairo_restore (ctx);
600 void calf_line_graph_expose_request (GtkWidget *widget, bool force)
602 // someone thinks we should redraw the line graph. let's see what
603 // the plugin thinks about. To do that a bitmask is sent to the
604 // plugin which can be changed. If the plugin returns true or if
605 // the request is in response of something like dragged handles, an
606 // exposition of the widget is requested from GTK
608 g_assert(CALF_IS_LINE_GRAPH(widget));
609 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
611 // quit if no source available
612 if (!lg->source) return;
614 if (lg->debug > 1) printf("\n\n### expose request %d ###\n", lg->generation);
616 // let a bitmask be switched by the plugin to determine the layers
617 // we want to draw. We set all cache layers to true if force_cache
618 // is set otherwise default is to draw nothing. The return value
619 // tells us whether the plugin wants to draw at all or not.
620 lg->layers = 0;
621 //if (lg->force_cache || lg->recreate_surfaces)
622 //lg->layers |= LG_CACHE_GRID | LG_CACHE_GRAPH | LG_CACHE_DOT | LG_CACHE_MOVING;
624 //if (lg->debug > 1) {
625 //printf("bitmask ");
626 //dsp::print_bits(sizeof(lg->layers), &lg->layers);
627 //printf("\n");
630 // if plugin returns true (something has obviously changed) or if
631 // the requestor forces a redraw, request an exposition of the widget
632 // from GTK
633 if (lg->source->get_layers(lg->source_id, lg->generation, lg->layers) or force)
634 gtk_widget_queue_draw(widget);
637 static gboolean
638 calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
640 g_assert(CALF_IS_LINE_GRAPH(widget));
641 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
643 // quit if no source available
644 if (!lg->source) return FALSE;
646 if (lg->debug) printf("\n\n####### exposing %d #######\n", lg->generation);
648 // cairo context of the window
649 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
651 // recreate surfaces if someone needs it (init of the widget,
652 // resizing the window..)
653 if (lg->recreate_surfaces) {
654 if (lg->debug) printf("recreation...\n");
655 calf_line_graph_create_surfaces(widget);
657 // all surfaces were recreated, so background is empty.
658 // draw the yellowish lighting on the background surface
659 cairo_t *bg = cairo_create(lg->background_surface);
660 if (lg->debug) printf("(draw background)\n");
661 line_graph_background(bg, 0, 0, lg->size_x, lg->size_y, lg->pad_x, lg->pad_y);
662 cairo_destroy(bg);
665 // the cache, grid and realtime surface wrapped in a cairo context
666 cairo_t *grid_c = cairo_create( lg->grid_surface );
667 cairo_t *cache_c = cairo_create( lg->cache_surface );
668 cairo_t *realtime_c = cairo_create( lg->realtime_surface );
670 if (lg->recreate_surfaces) {
671 // and copy it to the grid surface in case no grid is drawn
672 if (lg->debug) printf("copy bg->grid\n");
673 calf_line_graph_copy_surface(grid_c, lg->background_surface);
675 // and copy it to the cache surface in case no cache is drawn
676 if (lg->debug) printf("copy bg->cache\n");
677 calf_line_graph_copy_surface(cache_c, lg->background_surface);
679 // and copy it to the realtime surface in case no realtime is drawn
680 if (lg->debug) printf("copy bg->realtime\n");
681 calf_line_graph_copy_surface(realtime_c, lg->background_surface);
683 if (lg->recreate_surfaces or lg->force_redraw) {
684 // reset generation value and request a new expose event
685 lg->generation = 0;
686 lg->source->get_layers(lg->source_id, lg->generation, lg->layers);
689 int sx = lg->size_x;
690 int sy = lg->size_y;
691 int ox = lg->pad_x;
692 int oy = lg->pad_y;
694 if (lg->debug) printf("width: %d height: %d x: %d y: %d\n", sx, sy, ox, oy);
696 if (lg->debug) {
697 printf("bitmask ");
698 dsp::print_bits(sizeof(lg->layers), &lg->layers);
699 printf("\n");
702 // context used for the actual surface we want to draw on. It is
703 // switched over the drawing process via calf_line_graph_switch_context
704 cairo_t *ctx = NULL;
705 cairo_t *_ctx = NULL;
707 // the contexts for both moving curve caches
708 cairo_t *moving_c[2];
709 moving_c[0] = cairo_create( lg->moving_surface[0] );
710 moving_c[1] = cairo_create( lg->moving_surface[1] );
712 // the line widths to switch to between cycles
713 float grid_width = 1.0;
714 float graph_width = 1.5;
715 float dot_width = 0.0;
717 // more vars we have to initialize, mainly stuff we use in callback
718 // functions
719 float *data = new float[2 * std::max(lg->size_x, lg->size_y)];
720 float pos = 0;
721 bool vertical = false;
722 std::string legend = "";
723 int size = 0;
724 int direction = 0;
725 float x, y;
727 // a cairo wrapper to hand over contexts to the plugin for setting
728 // line colors, widths aso
729 cairo_impl cimpl;
730 cimpl.size_x = sx;
731 cimpl.size_y = sy;
732 cimpl.pad_x = ox;
733 cimpl.pad_y = oy;
735 // some state variables used to determine what has happened
736 bool realtime_drawn = false;
737 bool cache_drawn = false;
738 bool grid_drawn = false;
740 int drawing_phase;
742 // check if we can skip the whole drawing stuff and go on with
743 // copying everything we drawed before
745 if (!lg->layers)
746 goto finalize;
749 // 
753 if ( lg->force_cache
754 or lg->force_redraw
755 or lg->layers & LG_CACHE_GRID
756 or lg->layers & LG_CACHE_GRAPH
757 or lg->layers & LG_CACHE_DOT) {
758 if (lg->debug) printf("\n->cache\n");
760 // someone needs a redraw of the cache so start with the cache
761 // phase
762 drawing_phase = 0;
764 // set the right context to work with
765 _ctx = cache_c;
767 // and switch to grid surface in case we want to draw on it
768 if (lg->debug) printf("switch to grid\n");
769 ctx = calf_line_graph_switch_context(lg, grid_c, &cimpl);
770 } else {
771 if (lg->debug) printf("\n->realtime\n");
773 // no cache drawing neccessary, so skip the first drawing phase
774 drawing_phase = 1;
776 // set the right context to work with
777 _ctx = realtime_c;
779 // and switch to the realtime surface
780 if (lg->debug) printf("switch to realtime\n");
781 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
786 for (int phase = drawing_phase; phase < 2; phase++) {
787 // draw elements on the realtime and/or the cache surface
789 if (lg->debug) printf("\n### drawing phase %d\n", phase);
791 ///////////////////////////////////////////////////////////////
792 // GRID
793 ///////////////////////////////////////////////////////////////
795 if ((lg->layers & LG_CACHE_GRID and !phase) || (lg->layers & LG_REALTIME_GRID and phase)) {
796 // The plugin can set "vertical" to 1
797 // to force drawing of vertical lines instead of horizontal ones
798 // (which is the default)
799 // size and color of the grid (which can be set by the plugin
800 // via the context) are reset for every line.
801 if (!phase) {
802 // we're in cache phase and it seems we really want to
803 // draw new grid lines. so "clear" the grid surface
804 // with a pure background
805 if (lg->debug) printf("copy bg->grid\n");
806 calf_line_graph_copy_surface(ctx, lg->background_surface);
807 grid_drawn = true;
809 for (int a = 0;
810 legend = std::string(),
811 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.66),
812 cairo_set_line_width(ctx, grid_width),
813 lg->source->get_gridline(lg->source_id, a, phase, pos, vertical, legend, &cimpl);
814 a++)
816 if (!a and lg->debug) printf("(draw grid)\n");
817 calf_line_graph_draw_grid( lg, ctx, legend, vertical, pos );
820 if (!phase) {
821 // we're in cache phase so we have to switch back to
822 // the cache surface after drawing the grid on its surface
823 if (lg->debug) printf("switch to cache\n");
824 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
825 // if a grid was drawn copy it to cache
826 if (grid_drawn) {
827 if (lg->debug) printf("copy grid->cache\n");
828 calf_line_graph_copy_surface(ctx, lg->grid_surface);
829 cache_drawn = true;
833 ///////////////////////////////////////////////////////////////
834 // GRAPHS
835 ///////////////////////////////////////////////////////////////
837 if ((lg->layers & LG_CACHE_GRAPH and !phase) || (lg->layers & LG_REALTIME_GRAPH and phase)) {
838 // Cycle through all graphs and hand over the amount of horizontal
839 // pixels. The plugin is expected to set all corresponding vertical
840 // values in an array.
841 // size and color of the graph (which can be set by the plugin
842 // via the context) are reset for every graph.
844 if (!phase) {
845 // we are drawing the first graph in cache phase, so
846 // prepare the cache surface with the grid surface
847 if (lg->debug) printf("copy grid->cache\n");
848 calf_line_graph_copy_surface(ctx, lg->grid_surface);
849 cache_drawn = true;
850 } else if (!realtime_drawn) {
851 // we're in realtime phase and the realtime surface wasn't
852 // reset to cache by now (because there was no cache
853 // phase and no realtime grid was drawn)
854 // so "clear" the realtime surface with the cache
855 if (lg->debug) printf("copy cache->realtime\n");
856 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
857 realtime_drawn = true;
860 for(int a = 0;
861 lg->mode = 0,
862 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.8),
863 cairo_set_line_width(ctx, graph_width),
864 lg->source->get_graph(lg->source_id, a, phase, data, lg->size_x, &cimpl, &lg->mode);
865 a++)
867 if (lg->debug) printf("graph %d\n", a);
868 calf_line_graph_draw_graph( lg, ctx, data, lg->mode );
872 ///////////////////////////////////////////////////////////////
873 // MOVING
874 ///////////////////////////////////////////////////////////////
876 if ((lg->layers & LG_CACHE_MOVING and !phase) || (lg->layers & LG_REALTIME_MOVING and phase)) {
877 // we have a moving curve. switch to moving surface and
878 // clear it before we start to draw
879 if (lg->debug) printf("switch to moving %d\n", lg->movesurf);
880 ctx = calf_line_graph_switch_context(lg, moving_c[lg->movesurf], &cimpl);
881 calf_line_graph_clear_surface(ctx);
883 if (!phase and !cache_drawn) {
884 // we are drawing the first moving in cache phase and
885 // no cache has been created by now, so
886 // prepare the cache surface with the grid surface
887 if (lg->debug) printf("copy grid->cache\n");
888 calf_line_graph_copy_surface(cache_c, lg->grid_surface);
889 cache_drawn = true;
890 } else if (phase and !realtime_drawn) {
891 // we're in realtime phase and the realtime surface wasn't
892 // reset to cache by now (because there was no cache
893 // phase and no realtime grid was drawn)
894 // so "clear" the realtime surface with the cache
895 if (lg->debug) printf("copy cache->realtime\n");
896 calf_line_graph_copy_surface(realtime_c, lg->cache_surface);
897 realtime_drawn = true;
900 int a;
901 int offset;
902 int move = 0;
903 uint32_t color;
904 for(a = 0;
905 offset = a,
906 color = RGBAtoINT(0.35, 0.4, 0.2, 1),
907 lg->source->get_moving(lg->source_id, a, direction, data, lg->size_x, lg->size_y, offset, color);
908 a++)
910 if (lg->debug) printf("moving %d\n", a);
911 calf_line_graph_draw_moving(lg, ctx, data, direction, offset, color);
912 move += offset;
914 move ++;
915 // set moving distances according to direction
916 int x = 0;
917 int y = 0;
918 switch (direction) {
919 case LG_MOVING_LEFT:
920 default:
921 x = -move;
922 y = 0;
923 break;
924 case LG_MOVING_RIGHT:
925 x = move;
926 y = 0;
927 break;
928 case LG_MOVING_UP:
929 x = 0;
930 y = -move;
931 break;
932 case LG_MOVING_DOWN:
933 x = 0;
934 y = move;
935 break;
937 // copy the old moving surface to the right position on the
938 // new surface
939 if (lg->debug) printf("copy cached moving->moving\n");
940 cairo_set_source_surface(ctx, lg->moving_surface[(int)!lg->movesurf], x, y);
941 cairo_paint(ctx);
943 // switch back to the actual context
944 if (lg->debug) printf("switch to realtime/cache\n");
945 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
947 if (lg->debug) printf("copy moving->realtime/cache\n");
948 calf_line_graph_copy_surface(ctx, lg->moving_surface[lg->movesurf], 1);
950 // toggle the moving cache
951 lg->movesurf = (int)!lg->movesurf;
954 ///////////////////////////////////////////////////////////////
955 // DOTS
956 ///////////////////////////////////////////////////////////////
958 if ((lg->layers & LG_CACHE_DOT and !phase) || (lg->layers & LG_REALTIME_DOT and phase)) {
959 // Cycle through all dots. The plugin is expected to set the x
960 // and y value of the dot.
961 // color of the dot (which can be set by the plugin
962 // via the context) is reset for every graph.
964 if (!cache_drawn and !phase) {
965 // we are drawing dots in cache phase while
966 // the cache wasn't renewed (no graph was drawn), so
967 // prepare the cache surface with the grid surface
968 if (lg->debug) printf("copy grid->cache\n");
969 calf_line_graph_copy_surface(ctx, lg->grid_surface);
970 cache_drawn = true;
972 if (!realtime_drawn and phase) {
973 // we're in realtime phase and the realtime surface wasn't
974 // reset to cache by now (because there was no cache
975 // phase and no realtime grid or graph was drawn)
976 // so "clear" the realtime surface with the cache
977 if (lg->debug) printf("copy cache->realtime\n");
978 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
979 realtime_drawn = true;
981 for (int a = 0;
982 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 1),
983 cairo_set_line_width(ctx, dot_width),
984 lg->source->get_dot(lg->source_id, a, phase, x, y, size = 3, &cimpl);
985 a++)
987 if (lg->debug) printf("dot %d\n", a);
988 float yv = oy + sy / 2 - (sy / 2 - 1) * y;
989 cairo_arc(ctx, ox + x * sx, yv, size, 0, 2 * M_PI);
990 cairo_fill(ctx);
994 if (!phase) {
995 // if we have a second cycle for drawing on the realtime
996 // after the cache was renewed it's time to copy the
997 // cache to the realtime and switch the target surface
998 if (lg->debug) printf("switch to realtime\n");
999 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
1000 _ctx = realtime_c;
1002 if (cache_drawn) {
1003 // copy the cache to the realtime if it was changed
1004 if (lg->debug) printf("copy cache->realtime\n");
1005 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
1006 realtime_drawn = true;
1007 } else if (grid_drawn) {
1008 // copy the grid to the realtime if it was changed
1009 if (lg->debug) printf("copy grid->realtime\n");
1010 calf_line_graph_copy_surface(ctx, lg->grid_surface, lg->force_cache ? 1 : lg->fade);
1011 realtime_drawn = true;
1014 // check if we can skip the whole realtime phase
1015 if (!(lg->layers & LG_REALTIME_GRID)
1016 and !(lg->layers & LG_REALTIME_GRAPH)
1017 and !(lg->layers & LG_REALTIME_DOT)) {
1018 phase = 2;
1021 } // one or two cycles for drawing cached and non-cached elements
1024 // îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚
1027 finalize:
1028 delete[] data;
1029 if (lg->debug) printf("\n### finalize\n");
1031 // whatever happened - we need to copy the realtime surface to the
1032 // window surface
1033 //if (lg->debug) printf("switch to window\n");
1034 //ctx = calf_line_graph_switch_context(lg, c, &cimpl);
1035 if (lg->debug) printf("copy realtime->window\n");
1036 calf_line_graph_copy_surface(c, lg->realtime_surface);
1038 // if someone changed the handles via drag'n'drop or externally we
1039 // need a redraw of the handles surface
1040 if (lg->freqhandles and (lg->handle_redraw or lg->force_redraw)) {
1041 cairo_t *hs = cairo_create(lg->handles_surface);
1042 calf_line_graph_clear_surface(hs);
1043 calf_line_graph_draw_freqhandles(lg, hs);
1044 cairo_destroy(hs);
1047 // if we're using frequency handles we need to copy them to the
1048 // window
1049 if (lg->freqhandles) {
1050 if (lg->debug) printf("copy handles->window\n");
1051 calf_line_graph_copy_surface(c, lg->handles_surface);
1054 // and draw the crosshairs on top if neccessary
1055 if (lg->use_crosshairs && lg->crosshairs_active && lg->mouse_x > 0
1056 && lg->mouse_y > 0 && lg->handle_grabbed < 0) {
1057 std::string s;
1058 s = lg->source->get_crosshair_label((int)(lg->mouse_x - ox), (int)(lg->mouse_y - oy), sx, sy, &cimpl);
1059 cairo_set_line_width(c, 1),
1060 calf_line_graph_draw_crosshairs(lg, c, false, 0, 0.5, 5, false, lg->mouse_x - ox, lg->mouse_y - oy, s);
1063 lg->force_cache = false;
1064 lg->force_redraw = false;
1065 lg->handle_redraw = 0;
1066 lg->recreate_surfaces = 0;
1067 lg->layers = 0;
1069 // destroy all temporarily created cairo contexts
1070 cairo_destroy(c);
1071 cairo_destroy(realtime_c);
1072 cairo_destroy(grid_c);
1073 cairo_destroy(cache_c);
1074 cairo_destroy(moving_c[0]);
1075 cairo_destroy(moving_c[1]);
1077 lg->generation += 1;
1079 return TRUE;
1082 static int
1083 calf_line_graph_get_handle_at(CalfLineGraph *lg, double x, double y)
1085 int sx = lg->size_x;
1086 int sy = lg->size_y;
1087 int ox = lg->pad_x;
1088 int oy = lg->pad_y;
1090 sx += sx % 2 - 1;
1091 sy += sy % 2 - 1;
1093 // loop on all handles
1094 for (int i = 0; i < lg->freqhandles; i++) {
1095 FreqHandle *handle = &lg->freq_handles[i];
1096 if (!handle->is_active())
1097 continue;
1099 if (handle->dimensions == 1) {
1100 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1101 if (lg->mouse_x <= ox + round(handle->value_x * sx + HANDLE_WIDTH / 2.0) + 0.5 &&
1102 lg->mouse_x >= ox + round(handle->value_x * sx - HANDLE_WIDTH / 2.0) - 0.5 ) {
1103 return i;
1105 } else if (handle->dimensions >= 2) {
1106 double dx = lg->mouse_x - round(ox + handle->value_x * sx);
1107 double dy = lg->mouse_y - round(oy + handle->value_y * sy);
1109 // if mouse clicked inside circle of HANDLE_WIDTH
1110 if (sqrt(dx * dx + dy * dy) <= HANDLE_WIDTH / 2.0)
1111 return i;
1114 return -1;
1117 static gboolean
1118 calf_line_graph_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
1120 g_assert(CALF_IS_LINE_GRAPH(widget));
1121 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1123 int sx = lg->size_x;
1124 int sy = lg->size_y;
1125 int ox = lg->pad_x;
1126 int oy = lg->pad_y;
1128 sx += sx % 2 - 1;
1129 sy += sy % 2 - 1;
1131 lg->mouse_x = event->x;
1132 lg->mouse_y = event->y;
1134 if (lg->handle_grabbed >= 0) {
1135 FreqHandle *handle = &lg->freq_handles[lg->handle_grabbed];
1137 float new_x_value = float(event->x - ox) / float(sx);
1138 float new_y_value = float(event->y - oy) / float(sy);
1140 if (new_x_value < handle->left_bound) {
1141 new_x_value = handle->left_bound;
1142 } else if (new_x_value > handle->right_bound) {
1143 new_x_value = handle->right_bound;
1146 // restrict y range by top and bottom
1147 if (handle->dimensions >= 2) {
1148 if(new_y_value < 0.0) new_y_value = 0.0;
1149 if(new_y_value > 1.0) new_y_value = 1.0;
1152 if (new_x_value != handle->value_x ||
1153 new_y_value != handle->value_y) {
1154 handle->value_x = new_x_value;
1155 handle->value_y = new_y_value;
1157 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1159 lg->handle_redraw = 1;
1160 calf_line_graph_expose_request(widget, true);
1162 if (event->is_hint)
1164 gdk_event_request_motions(event);
1167 int handle_hovered = calf_line_graph_get_handle_at(lg, event->x, event->y);
1168 if (handle_hovered != lg->handle_hovered) {
1169 if (lg->handle_grabbed >= 0 ||
1170 handle_hovered != -1) {
1171 gdk_window_set_cursor(widget->window, lg->hand_cursor);
1172 lg->handle_hovered = handle_hovered;
1173 } else {
1174 gdk_window_set_cursor(widget->window, lg->arrow_cursor);
1175 lg->handle_hovered = -1;
1177 lg->handle_redraw = 1;
1178 calf_line_graph_expose_request(widget, true);
1180 if(lg->crosshairs_active and lg->use_crosshairs) {
1181 calf_line_graph_expose_request(widget, true);
1183 return TRUE;
1186 static gboolean
1187 calf_line_graph_button_press (GtkWidget *widget, GdkEventButton *event)
1189 g_assert(CALF_IS_LINE_GRAPH(widget));
1190 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1191 bool inside_handle = false;
1193 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1194 if (i != -1)
1196 FreqHandle *handle = &lg->freq_handles[i];
1198 if (handle->dimensions == 1) {
1199 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1200 lg->handle_grabbed = i;
1201 inside_handle = true;
1203 if (lg->enforce_handle_order) {
1204 // look for previous one dimensional handle to find left_bound
1205 for (int j = i - 1; j >= 0; j--) {
1206 FreqHandle *prevhandle = &lg->freq_handles[j];
1207 if(prevhandle->is_active() && prevhandle->dimensions == 1) {
1208 handle->left_bound = prevhandle->value_x + lg->min_handle_distance;
1209 break;
1213 // look for next one dimensional handle to find right_bound
1214 for (int j = i + 1; j < lg->freqhandles; j++) {
1215 FreqHandle *nexthandle = &lg->freq_handles[j];
1216 if(nexthandle->is_active() && nexthandle->dimensions == 1) {
1217 handle->right_bound = nexthandle->value_x - lg->min_handle_distance;
1218 break;
1222 } else if (handle->dimensions >= 2) {
1223 lg->handle_grabbed = i;
1224 inside_handle = true;
1228 if (inside_handle && event->type == GDK_2BUTTON_PRESS) {
1229 FreqHandle &handle = lg->freq_handles[lg->handle_grabbed];
1230 handle.value_x = handle.default_value_x;
1231 handle.value_y = handle.default_value_y;
1232 g_signal_emit_by_name(widget, "freqhandle-changed", &handle);
1235 if(!inside_handle) {
1236 lg->crosshairs_active = !lg->crosshairs_active;
1239 calf_line_graph_expose_request(widget, true);
1240 gtk_widget_grab_focus(widget);
1241 gtk_grab_add(widget);
1243 return TRUE;
1246 static gboolean
1247 calf_line_graph_button_release (GtkWidget *widget, GdkEventButton *event)
1249 g_assert(CALF_IS_LINE_GRAPH(widget));
1250 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1252 lg->handle_grabbed = -1;
1254 if (GTK_WIDGET_HAS_GRAB(widget))
1255 gtk_grab_remove(widget);
1257 calf_line_graph_expose_request(widget, true);
1258 return TRUE;
1261 static gboolean
1262 calf_line_graph_scroll (GtkWidget *widget, GdkEventScroll *event)
1264 g_assert(CALF_IS_LINE_GRAPH(widget));
1265 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1267 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1268 if (i != -1)
1270 FreqHandle *handle = &lg->freq_handles[i];
1271 if (handle->dimensions == 3) {
1272 if (event->direction == GDK_SCROLL_UP) {
1273 handle->value_z += 0.05;
1274 if(handle->value_z > 1.0) {
1275 handle->value_z = 1.0;
1277 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1278 } else if (event->direction == GDK_SCROLL_DOWN) {
1279 handle->value_z -= 0.05;
1280 if(handle->value_z < 0.0) {
1281 handle->value_z = 0.0;
1283 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1285 lg->handle_redraw = 1;
1288 return TRUE;
1291 static gboolean
1292 calf_line_graph_enter (GtkWidget *widget, GdkEventCrossing *event)
1294 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1296 if (lg->debug) printf("[enter]\n");
1297 return TRUE;
1300 static gboolean
1301 calf_line_graph_leave (GtkWidget *widget, GdkEventCrossing *event)
1304 g_assert(CALF_IS_LINE_GRAPH(widget));
1305 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1307 if (lg->debug) printf("[leave]\n");
1308 if (lg->mouse_x >= 0 or lg->mouse_y >= 0)
1309 calf_line_graph_expose_request(widget, true);
1310 lg->mouse_x = -1;
1311 lg->mouse_y = -1;
1313 return TRUE;
1317 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
1319 g_assert(CALF_IS_LINE_GRAPH(graph));
1320 graph->is_square = is_square;
1323 static void
1324 calf_line_graph_size_request (GtkWidget *widget,
1325 GtkRequisition *requisition)
1327 g_assert(CALF_IS_LINE_GRAPH(widget));
1329 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1331 if (lg->debug) printf("size request\n");
1334 static void
1335 calf_line_graph_size_allocate (GtkWidget *widget,
1336 GtkAllocation *allocation)
1338 g_assert(CALF_IS_LINE_GRAPH(widget));
1339 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1341 if (lg->debug) printf("size allocation\n");
1343 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
1345 // remember the allocation
1346 widget->allocation = *allocation;
1348 // reset the allocation if a square widget is requested
1349 GtkAllocation &a = widget->allocation;
1350 if (lg->is_square)
1352 if (a.width > a.height)
1354 a.x += (a.width - a.height) / 2;
1355 a.width = a.height;
1357 if (a.width < a.height)
1359 a.y += (a.height - a.width) / 2;
1360 a.height = a.width;
1364 lg->size_x = a.width - lg->pad_x * 2;
1365 lg->size_y = a.height - lg->pad_y * 2;
1367 lg->recreate_surfaces = 1;
1368 parent_class->size_allocate( widget, &a );
1372 static void
1373 calf_line_graph_class_init (CalfLineGraphClass *klass)
1375 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1376 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1377 widget_class->expose_event = calf_line_graph_expose;
1378 widget_class->size_request = calf_line_graph_size_request;
1379 widget_class->size_allocate = calf_line_graph_size_allocate;
1380 widget_class->button_press_event = calf_line_graph_button_press;
1381 widget_class->button_release_event = calf_line_graph_button_release;
1382 widget_class->motion_notify_event = calf_line_graph_pointer_motion;
1383 widget_class->scroll_event = calf_line_graph_scroll;
1384 widget_class->enter_notify_event = calf_line_graph_enter;
1385 widget_class->leave_notify_event = calf_line_graph_leave;
1387 g_signal_new("freqhandle-changed",
1388 G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST,
1389 0, NULL, NULL,
1390 g_cclosure_marshal_VOID__POINTER,
1391 G_TYPE_NONE, 1, G_TYPE_POINTER);
1394 static void
1395 calf_line_graph_unrealize (GtkWidget *widget, CalfLineGraph *lg)
1397 if (lg->debug) printf("unrealize\n");
1398 calf_line_graph_destroy_surfaces(widget);
1401 static void
1402 calf_line_graph_init (CalfLineGraph *lg)
1404 GtkWidget *widget = GTK_WIDGET(lg);
1406 if (lg->debug) printf("lg init\n");
1408 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS | GTK_SENSITIVE | GTK_PARENT_SENSITIVE);
1409 gtk_widget_add_events(widget, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1411 widget->requisition.width = 40;
1412 widget->requisition.height = 40;
1413 lg->force_cache = true;
1414 lg->force_redraw = false;
1415 lg->zoom = 1;
1416 lg->param_zoom = -1;
1417 lg->offset = 0;
1418 lg->param_offset = -1;
1419 lg->recreate_surfaces = 1;
1420 lg->mode = 0;
1421 lg->movesurf = 0;
1422 lg->generation = 0;
1423 lg->arrow_cursor = gdk_cursor_new(GDK_RIGHT_PTR);
1424 lg->hand_cursor = gdk_cursor_new(GDK_FLEUR);
1425 lg->layers = LG_CACHE_GRID | LG_CACHE_GRAPH
1426 | LG_CACHE_DOT | LG_CACHE_MOVING
1427 | LG_REALTIME_GRID | LG_REALTIME_GRAPH
1428 | LG_REALTIME_DOT | LG_REALTIME_MOVING;
1430 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_line_graph_unrealize), (gpointer)lg);
1432 for(int i = 0; i < FREQ_HANDLES; i++) {
1433 FreqHandle *handle = &lg->freq_handles[i];
1434 handle->active = false;
1435 handle->param_active_no = -1;
1436 handle->param_x_no = -1;
1437 handle->param_y_no = -1;
1438 handle->value_x = -1.0;
1439 handle->value_y = -1.0;
1440 handle->param_x_no = -1;
1441 handle->label = NULL;
1442 handle->left_bound = 0.0 + lg->min_handle_distance;
1443 handle->right_bound = 1.0 - lg->min_handle_distance;
1446 lg->handle_grabbed = -1;
1447 lg->handle_hovered = -1;
1448 lg->handle_redraw = 1;
1449 lg->min_handle_distance = 0.025;
1451 lg->background_surface = NULL;
1452 lg->grid_surface = NULL;
1453 lg->cache_surface = NULL;
1454 lg->moving_surface[0] = NULL;
1455 lg->moving_surface[1] = NULL;
1456 lg->handles_surface = NULL;
1457 lg->realtime_surface = NULL;
1460 GtkWidget *
1461 calf_line_graph_new()
1463 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
1466 GType
1467 calf_line_graph_get_type (void)
1469 static GType type = 0;
1470 if (!type) {
1471 static const GTypeInfo type_info = {
1472 sizeof(CalfLineGraphClass),
1473 NULL, /* base_init */
1474 NULL, /* base_finalize */
1475 (GClassInitFunc)calf_line_graph_class_init,
1476 NULL, /* class_finalize */
1477 NULL, /* class_data */
1478 sizeof(CalfLineGraph),
1479 0, /* n_preallocs */
1480 (GInstanceInitFunc)calf_line_graph_init
1483 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
1485 for (int i = 0; ; i++) {
1486 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
1487 if (g_type_from_name(name)) {
1488 free(name);
1489 continue;
1491 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
1492 name,
1493 type_info_copy,
1494 (GTypeFlags)0);
1495 free(name);
1496 break;
1499 return type;