hz_to_note as primitive; line graph cross hair with much more information; new labels...
[calf.git] / src / ctl_linegraph.cpp
blob2ee5ddeeda5c4de0f0281995de006491f801ebd2
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/drawingutils.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>
32 #include <algorithm>
34 #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)
35 #define INTtoR(color) (float)((color & 0xff000000) >> 24) / 255.f
36 #define INTtoG(color) (float)((color & 0x00ff0000) >> 16) / 255.f
37 #define INTtoB(color) (float)((color & 0x0000ff00) >> 8) / 255.f
38 #define INTtoA(color) (float)((color & 0x000000ff) >> 0) / 255.f
40 using namespace calf_plugins;
42 static void
43 calf_line_graph_draw_grid( CalfLineGraph* lg, cairo_t *ctx, std::string &legend, bool vertical, float pos )
45 int sx = lg->size_x;
46 int sy = lg->size_y;
47 int ox = lg->pad_x;
48 int oy = lg->pad_y;
50 float x = 0, y = 0;
52 cairo_text_extents_t tx;
53 int size;
54 if (!legend.empty()) {
55 cairo_text_extents(ctx, legend.c_str(), &tx);
56 size = vertical ? tx.height : tx.width;
57 size += 5;
58 } else {
59 size = 0;
62 if (vertical)
64 x = floor(ox + pos * sx) + 0.5;
65 cairo_move_to(ctx, x, oy);
66 cairo_line_to(ctx, x, oy + sy - size);
67 cairo_stroke(ctx);
68 if (!legend.empty()) {
69 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
70 cairo_move_to(ctx, x - (tx.x_bearing + tx.width / 2.0), oy + sy - 2);
71 cairo_show_text(ctx, legend.c_str());
74 else
76 y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
77 cairo_move_to(ctx, ox, y);
78 cairo_line_to(ctx, ox + sx - size, y);
79 cairo_stroke(ctx);
81 if (!legend.empty()) {
82 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
83 cairo_move_to(ctx, ox + sx - 4 - tx.width, y + tx.height/2 - 2);
84 cairo_show_text(ctx, legend.c_str());
87 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());
90 static int
91 calf_line_graph_draw_graph( CalfLineGraph* lg, cairo_t *ctx, float *data, int mode = 0 )
93 if (lg->debug) printf("(draw graph)\n");
95 int sx = lg->size_x;
96 int sy = lg->size_y;
97 int ox = lg->pad_x;
98 int oy = lg->pad_y;
100 int _lastx = 0;
101 float y = 0.f;
102 int startdraw = -1;
104 for (int i = 0; i < sx; i++) {
105 y = (oy + sy / 2 - (sy / 2 - 1) * data[i]);
106 if (lg->debug > 2) printf("* graph x: %d, y: %.5f, data: %.5f\n", i, y, data[i]);
107 switch (mode) {
108 case 0:
109 case 1:
110 default:
111 // we want to draw a line
112 if (i and (data[i] < INFINITY or i == sx - 1)) {
113 cairo_line_to(ctx, ox + i, y);
114 } else if (i and startdraw >= 0) {
115 continue;
116 } else {
117 cairo_move_to(ctx, ox, y);
118 if (startdraw < 0)
119 startdraw = i;
121 break;
122 case 2:
123 // bars are used
124 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
125 cairo_rectangle(ctx, ox + _lastx, (int)y, i - _lastx, sy - (int)y + oy);
126 _lastx = i;
127 if (startdraw < 0)
128 startdraw = ox + _lastx;
129 } else {
130 continue;
132 break;
133 case 3:
134 // this one is drawing little boxes at the values position
135 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
136 cairo_rectangle(ctx, ox + _lastx, (int)y - 1, i - _lastx, 2);
137 _lastx = i;
138 if (startdraw < 0)
139 startdraw = ox + _lastx;
140 } else {
141 continue;
143 break;
144 case 4:
145 // this one is drawing bars centered on the x axis
146 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
147 cairo_rectangle(ctx, ox + _lastx, oy + sy / 2, i - _lastx, -1 * data[i] * (sy / 2));
148 _lastx = i;
149 if (startdraw < 0)
150 startdraw = ox + _lastx;
151 } else {
152 continue;
154 break;
155 case 5:
156 // this one is drawing bars centered on the x axis with 1
157 // as the center
158 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
159 cairo_rectangle(ctx, ox + _lastx,oy + sy / 2 - sy * lg->offset / 2, i - _lastx, -1 * data[i] * (sy / 2) + sy * lg->offset / 2);
160 _lastx = i;
161 if (startdraw < 0)
162 startdraw = ox + _lastx;
163 } else {
164 continue;
166 break;
169 if (mode == 1) {
170 cairo_line_to(ctx, sx + 2 * ox, sy + 2 * oy);
171 cairo_line_to(ctx, 0, sy + 2 * oy);
172 cairo_close_path(ctx);
174 if(!mode) {
175 cairo_stroke(ctx);
176 } else {
177 cairo_fill(ctx);
179 return startdraw;
182 static void
183 calf_line_graph_draw_moving(CalfLineGraph* lg, cairo_t *ctx, float *data, int direction, int offset, int color)
185 if (lg->debug) printf("(draw moving)\n");
187 int sx = lg->size_x;
188 int sy = lg->size_y;
189 int ox = lg->pad_x;
190 int oy = lg->pad_y;
192 int _last = 0;
193 int startdraw = -1;
195 int sm = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? sx : sy);
196 int om = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? ox : oy);
197 for (int i = 0; i < sm; i++) {
198 if (lg->debug > 2) printf("* moving i: %d, dir: %d, offset: %d, data: %.5f\n", i, direction, offset, data[i]);
199 if (i and ((data[i] < INFINITY) or i >= sm)) {
200 cairo_set_source_rgba(ctx, INTtoR(color), INTtoG(color), INTtoB(color), (data[i] + 1) / 1.4 * INTtoA(color));
201 switch (direction) {
202 case LG_MOVING_LEFT:
203 default:
204 cairo_rectangle(ctx, ox + sx - 1 - offset, oy + _last, 1, i - _last);
205 break;
206 case LG_MOVING_RIGHT:
207 cairo_rectangle(ctx, ox + offset, oy + _last, 1, i - _last);
208 break;
209 case LG_MOVING_UP:
210 cairo_rectangle(ctx, ox + _last, oy + sy - 1 - offset, i - _last, 1);
211 break;
212 case LG_MOVING_DOWN:
213 cairo_rectangle(ctx, ox + _last, oy + offset, i - _last, 1);
214 break;
217 cairo_fill(ctx);
218 _last = i;
219 if (startdraw < 0)
220 startdraw = om + _last;
222 else
223 continue;
228 void calf_line_graph_draw_label(cairo_t *cache_cr, std::string label, int x, int y)
230 if (label.empty())
231 return;
232 int n = int(std::count(label.begin(), label.end(), '\n')) + 1;
233 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.5);
234 std::string::size_type lpos = label.find_first_not_of("\n", 0);
235 std::string::size_type pos = label.find_first_of("\n", lpos);
236 int p = 0;
237 while (std::string::npos != pos || std::string::npos != lpos) {
238 const char *str = label.substr(lpos, pos - lpos).c_str();
239 printf("%s\n", str);
240 cairo_text_extents_t tx;
241 cairo_text_extents(cache_cr, str, &tx);
242 if (!p)
243 p = y - 3 - (n / 2) * (tx.height + 4);
244 p += tx.height + 4;
245 cairo_move_to(cache_cr, x - 8 - tx.width, p);
246 printf("%s\n", str);
247 cairo_show_text(cache_cr, str);
248 lpos = label.find_first_not_of("\n", pos);
249 pos = label.find_first_of("\n", lpos);
253 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)
255 if (lg->debug) printf("(draw crosshairs)\n");
256 // crosshairs
258 int sx = lg->size_x;
259 int sy = lg->size_y;
260 int ox = lg->pad_x;
261 int oy = lg->pad_y;
263 int _x = ox + x;
264 int _y = ox + y;
266 cairo_pattern_t *pat;
268 if(mask > 0 and circle) {
269 cairo_move_to(cache_cr, _x, _y);
270 cairo_arc (cache_cr, _x, _y, mask, 0, 2 * M_PI);
271 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
272 cairo_fill(cache_cr);
273 if (alpha < 0.3) {
274 cairo_move_to(cache_cr, _x, _y);
275 cairo_arc (cache_cr, _x, _y, HANDLE_WIDTH / 2, 0, 2 * M_PI);
276 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.2);
277 cairo_fill(cache_cr);
280 if(gradient and gradient_rad > 0) {
281 // draw the crosshairs with a steady gradient around
282 pat = cairo_pattern_create_radial(_x, _y, 1, _x, _y, gradient_rad * 2);
283 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
284 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
285 // top
286 cairo_rectangle(cache_cr, _x, _y - gradient_rad, 1, gradient_rad - mask);
287 // right
288 cairo_rectangle(cache_cr, _x + mask, _y, gradient_rad - mask, 1);
289 // bottom
290 cairo_rectangle(cache_cr, _x, _y + mask, 1, gradient_rad - mask);
291 // left
292 cairo_rectangle(cache_cr, _x - gradient_rad, _y, gradient_rad - mask, 1);
294 cairo_set_source(cache_cr, pat);
295 cairo_fill(cache_cr);
296 } else if(gradient) {
297 // draw the crosshairs with a gradient to the frame
298 // top
299 cairo_rectangle(cache_cr, _x, oy, 1, y - mask);
300 pat = cairo_pattern_create_linear(_x, oy, _x, _y);
301 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
302 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
303 cairo_set_source(cache_cr, pat);
304 cairo_fill(cache_cr);
305 // right
306 cairo_rectangle(cache_cr, _x + mask, _y, sx - x - mask, 1);
307 pat = cairo_pattern_create_linear(_x, oy, sx, oy);
308 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
309 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
310 cairo_set_source(cache_cr, pat);
311 cairo_fill(cache_cr);
312 // bottom
313 cairo_rectangle(cache_cr, _x, _y + mask, 1, sy - y - mask);
314 pat = cairo_pattern_create_linear(_x, _y, _x, oy + sy);
315 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
316 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
317 cairo_set_source(cache_cr, pat);
318 cairo_fill(cache_cr);
319 // left
320 cairo_rectangle(cache_cr, ox, _y, x - mask, 1);
321 pat = cairo_pattern_create_linear(ox, oy, _x, oy);
322 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
323 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
324 cairo_set_source(cache_cr, pat);
325 cairo_fill(cache_cr);
326 } else {
327 // draw normal crosshairs
328 // top
329 cairo_move_to(cache_cr, _x + 0.5, oy + 0.5);
330 cairo_line_to(cache_cr, _x + 0.5, _y - mask + 0.5);
331 // right
332 cairo_move_to(cache_cr, _x + mask + 0.5, _y + 0.5);
333 cairo_line_to(cache_cr, ox + sx + 0.5, _y + 0.5);
334 // bottom
335 cairo_move_to(cache_cr, _x + 0.5, _y + mask + 0.5);
336 cairo_line_to(cache_cr, _x + 0.5, oy + sy + 0.5);
337 // left
338 cairo_move_to(cache_cr, ox + 0.5, _y + 0.5);
339 cairo_line_to(cache_cr, _x - mask + 0.5, _y + 0.5);
341 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
342 cairo_stroke(cache_cr);
344 calf_line_graph_draw_label(cache_cr, label, x, y);
347 void calf_line_graph_draw_freqhandles(CalfLineGraph* lg, cairo_t* c)
349 // freq_handles
350 if (lg->debug) printf("(draw handles)\n");
352 int sx = lg->size_x;
353 int sy = lg->size_y;
354 int ox = lg->pad_x;
355 int oy = lg->pad_y;
357 if (lg->freqhandles > 0) {
358 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 1.0);
359 cairo_set_line_width(c, 1.0);
361 for (int i = 0; i < lg->freqhandles; i++) {
362 FreqHandle *handle = &lg->freq_handles[i];
363 if(!handle->is_active())
364 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 char label[256];
372 // choose colors between dragged and normal state
373 if (lg->handle_hovered == i) {
374 pat_alpha = 0.3;
375 grad = false;
376 cairo_set_source_rgba(c, 0, 0, 0, 0.7);
377 } else {
378 pat_alpha = 0.1;
379 grad = true;
380 //cairo_set_source_rgb(c, 0.44, 0.5, 0.21);
381 cairo_set_source_rgba(c, 0, 0, 0, 0.5);
383 if (handle->dimensions >= 2) {
384 cairo_move_to(c, val_x + 8, val_y);
385 } else {
386 cairo_move_to(c, val_x + 11, oy + 15);
389 if (handle->dimensions == 1) {
390 // draw the main line
391 cairo_move_to(c, ox + val_x + 0.5, oy);
392 cairo_line_to(c, ox + val_x + 0.5, oy + sy);
393 cairo_stroke(c);
394 // draw some one-dimensional bling-bling
395 cairo_pattern_t *pat;
396 switch(handle->style) {
397 default:
398 case 0:
399 // bell filters, default
400 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
401 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
402 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha);
403 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
404 cairo_rectangle(c, ox + val_x - 7, oy, 6, sy);
405 cairo_rectangle(c, ox + val_x + 2, oy, 6, sy);
406 break;
407 case 1:
408 // hipass
409 pat = cairo_pattern_create_linear(ox, oy, ox + val_x, oy);
410 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
411 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, pat_alpha);
412 cairo_rectangle(c, ox, oy, val_x - 1, sy);
413 break;
414 case 2:
415 // loshelf
416 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
417 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
418 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
419 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
420 cairo_rectangle(c, ox, oy, val_x - 1, sy);
421 break;
422 case 3:
423 // hishelf
424 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
425 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
426 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
427 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
428 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 2, sy);
429 break;
430 case 4:
431 // lopass
432 pat = cairo_pattern_create_linear(ox + val_x, oy, ox + sx, oy);
433 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, pat_alpha);
434 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
435 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 1, sy);
436 break;
438 cairo_set_source(c, pat);
439 cairo_fill(c);
440 cairo_pattern_destroy(pat);
441 float freq = exp((handle->value_x) * log(1000)) * 20.0;
443 if (handle->label && strlen(handle->label))
444 sprintf(label, "%.2f Hz\n%s", freq, handle->label);
445 else
446 sprintf(label, "%.2f Hz", freq);
447 calf_line_graph_draw_label(c, label, val_x, oy + 15);
448 } else {
449 float freq = exp((handle->value_x) * log(1000)) * 20.0;
451 if (handle->label && strlen(handle->label))
452 sprintf(label, "%.2f Hz\n%.2f dB\n%s", freq, dsp::amp2dB(handle->value_y), handle->label);
453 else
454 sprintf(label, "%.2f Hz\n%.2f dB", freq, dsp::amp2dB(handle->value_y));
455 int mask = 30 - log10(1 + handle->value_z * 9) * 30 + HANDLE_WIDTH / 2.f;
456 calf_line_graph_draw_crosshairs(lg, c, grad, -1, pat_alpha, mask, true, val_x, val_y, std::string(label));
463 static void
464 calf_line_graph_destroy_surfaces (GtkWidget *widget)
466 g_assert(CALF_IS_LINE_GRAPH(widget));
467 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
469 if (lg->debug) printf("{destroy surfaces}\n");
471 // destroy all surfaces - and don't tell anybody about it - hehe
472 if( lg->background_surface )
473 cairo_surface_destroy( lg->background_surface );
474 if( lg->grid_surface )
475 cairo_surface_destroy( lg->grid_surface );
476 if( lg->cache_surface )
477 cairo_surface_destroy( lg->cache_surface );
478 if( lg->moving_surface[0] )
479 cairo_surface_destroy( lg->moving_surface[0] );
480 if( lg->moving_surface[1] )
481 cairo_surface_destroy( lg->moving_surface[1] );
482 if( lg->handles_surface )
483 cairo_surface_destroy( lg->handles_surface );
484 if( lg->realtime_surface )
485 cairo_surface_destroy( lg->realtime_surface );
487 static void
488 calf_line_graph_create_surfaces (GtkWidget *widget)
490 g_assert(CALF_IS_LINE_GRAPH(widget));
491 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
493 if (lg->debug) printf("{create surfaces}\n");
495 int width = widget->allocation.width;
496 int height = widget->allocation.height;
498 // the size of the "real" drawing area
499 lg->size_x = width - lg->pad_x * 2;
500 lg->size_y = height - lg->pad_y * 2;
502 calf_line_graph_destroy_surfaces(widget);
503 // create the background surface.
504 // background holds the graphics of the frame and the yellowish
505 // background light for faster redrawing of static stuff
506 lg->background_surface = cairo_image_surface_create(
507 CAIRO_FORMAT_ARGB32, width, height );
509 // create the grid surface.
510 // this one is used as a cache for the grid on the background in the
511 // cache phase. If a graph or dot in cache phase needs to be redrawn
512 // we don't need to redraw the whole grid.
513 lg->grid_surface = cairo_image_surface_create(
514 CAIRO_FORMAT_ARGB32, width, height );
516 // create the cache surface.
517 // cache holds a copy of the background with a static part of
518 // the grid and some static curves and dots.
519 lg->cache_surface = cairo_image_surface_create(
520 CAIRO_FORMAT_ARGB32, width, height );
522 // create the moving surface.
523 // moving is used as a cache for any slowly moving graphics like
524 // spectralizer or waveforms
525 lg->moving_surface[0] = cairo_image_surface_create(
526 CAIRO_FORMAT_ARGB32, width, height );
528 // create the moving temp surface.
529 // moving is used as a cache for any slowly moving graphics like
530 // spectralizer or waveforms
531 lg->moving_surface[1] = cairo_image_surface_create(
532 CAIRO_FORMAT_ARGB32, width, height );
534 // create the handles surface.
535 // this one contains the handles graphics to avoid redrawing
536 // each cycle
537 lg->handles_surface = cairo_image_surface_create(
538 CAIRO_FORMAT_ARGB32, width, height );
540 // create the realtime surface.
541 // realtime is used to cache the realtime graphics for drawing the
542 // crosshairs on top if nothing else changed
543 lg->realtime_surface = cairo_image_surface_create(
544 CAIRO_FORMAT_ARGB32, width, height );
546 lg->force_cache = true;
549 static cairo_t
550 *calf_line_graph_switch_context(CalfLineGraph* lg, cairo_t *ctx, cairo_impl *cimpl)
552 if (lg->debug) printf("{switch context}\n");
554 cimpl->context = ctx;
555 cairo_select_font_face(ctx, "Sans",
556 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
557 cairo_set_font_size(ctx, 9);
558 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER);
559 cairo_rectangle(ctx, lg->pad_x, lg->pad_y, lg->size_x, lg->size_y);
560 cairo_clip(ctx);
561 return ctx;
564 static void
565 calf_line_graph_copy_surface(cairo_t *ctx, cairo_surface_t *source, float fade = 1.f)
567 // copy a surface to a cairo context
568 cairo_save(ctx);
569 cairo_set_source_surface(ctx, source, 0, 0);
570 if (fade < 1.0) {
571 cairo_paint_with_alpha(ctx, fade * 0.35 + 0.05);
572 } else {
573 cairo_paint(ctx);
575 cairo_restore(ctx);
578 static void
579 calf_line_graph_clear_surface(cairo_t *ctx)
581 // clears a surface to transparent
582 cairo_save (ctx);
583 cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
584 cairo_paint (ctx);
585 cairo_restore (ctx);
588 void calf_line_graph_expose_request (GtkWidget *widget, bool force)
590 // someone thinks we should redraw the line graph. let's see what
591 // the plugin thinks about. To do that a bitmask is sent to the
592 // plugin which can be changed. If the plugin returns true or if
593 // the request is in response of something like dragged handles, an
594 // exposition of the widget is requested from GTK
596 g_assert(CALF_IS_LINE_GRAPH(widget));
597 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
599 // quit if no source available
600 if (!lg->source) return;
602 if (lg->debug > 1) printf("\n\n### expose request %d ###\n", lg->generation);
604 // let a bitmask be switched by the plugin to determine the layers
605 // we want to draw. We set all cache layers to true if force_cache
606 // is set otherwise default is to draw nothing. The return value
607 // tells us whether the plugin wants to draw at all or not.
608 lg->layers = 0;
609 //if (lg->force_cache || lg->recreate_surfaces)
610 //lg->layers |= LG_CACHE_GRID | LG_CACHE_GRAPH | LG_CACHE_DOT | LG_CACHE_MOVING;
612 //if (lg->debug > 1) {
613 //printf("bitmask ");
614 //dsp::print_bits(sizeof(lg->layers), &lg->layers);
615 //printf("\n");
618 // if plugin returns true (something has obviously changed) or if
619 // the requestor forces a redraw, request an exposition of the widget
620 // from GTK
621 if (lg->source->get_layers(lg->source_id, lg->generation, lg->layers) or force)
622 gtk_widget_queue_draw(widget);
625 static gboolean
626 calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
628 g_assert(CALF_IS_LINE_GRAPH(widget));
629 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
631 // quit if no source available
632 if (!lg->source) return FALSE;
634 if (lg->debug) printf("\n\n####### exposing %d #######\n", lg->generation);
636 // cairo context of the window
637 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
639 // recreate surfaces if someone needs it (init of the widget,
640 // resizing the window..)
641 if (lg->recreate_surfaces) {
642 if (lg->debug) printf("recreation...\n");
643 calf_line_graph_create_surfaces(widget);
645 // all surfaces were recreated, so background is empty.
646 // draw the yellowish lighting on the background surface
647 cairo_t *bg = cairo_create(lg->background_surface);
648 if (lg->debug) printf("(draw background)\n");
649 display_background(widget, bg, 0, 0, lg->size_x, lg->size_y, lg->pad_x, lg->pad_y);
650 cairo_destroy(bg);
653 // the cache, grid and realtime surface wrapped in a cairo context
654 cairo_t *grid_c = cairo_create( lg->grid_surface );
655 cairo_t *cache_c = cairo_create( lg->cache_surface );
656 cairo_t *realtime_c = cairo_create( lg->realtime_surface );
658 if (lg->recreate_surfaces) {
659 // and copy it to the grid surface in case no grid is drawn
660 if (lg->debug) printf("copy bg->grid\n");
661 calf_line_graph_copy_surface(grid_c, lg->background_surface);
663 // and copy it to the cache surface in case no cache is drawn
664 if (lg->debug) printf("copy bg->cache\n");
665 calf_line_graph_copy_surface(cache_c, lg->background_surface);
667 // and copy it to the realtime surface in case no realtime is drawn
668 if (lg->debug) printf("copy bg->realtime\n");
669 calf_line_graph_copy_surface(realtime_c, lg->background_surface);
671 if (lg->recreate_surfaces or lg->force_redraw) {
672 // reset generation value and request a new expose event
673 lg->generation = 0;
674 lg->source->get_layers(lg->source_id, lg->generation, lg->layers);
677 int sx = lg->size_x;
678 int sy = lg->size_y;
679 int ox = lg->pad_x;
680 int oy = lg->pad_y;
682 if (lg->debug) printf("width: %d height: %d x: %d y: %d\n", sx, sy, ox, oy);
684 if (lg->debug) {
685 printf("bitmask ");
686 dsp::print_bits(sizeof(lg->layers), &lg->layers);
687 printf("\n");
690 // context used for the actual surface we want to draw on. It is
691 // switched over the drawing process via calf_line_graph_switch_context
692 cairo_t *ctx = NULL;
693 cairo_t *_ctx = NULL;
695 // the contexts for both moving curve caches
696 cairo_t *moving_c[2];
697 moving_c[0] = cairo_create( lg->moving_surface[0] );
698 moving_c[1] = cairo_create( lg->moving_surface[1] );
700 // the line widths to switch to between cycles
701 float grid_width = 1.0;
702 float graph_width = 1.5;
703 float dot_width = 0.0;
705 // more vars we have to initialize, mainly stuff we use in callback
706 // functions
707 float *data = new float[2 * std::max(lg->size_x, lg->size_y)];
708 float pos = 0;
709 bool vertical = false;
710 std::string legend = "";
711 int size = 0;
712 int direction = 0;
713 float x, y;
715 // a cairo wrapper to hand over contexts to the plugin for setting
716 // line colors, widths aso
717 cairo_impl cimpl;
718 cimpl.size_x = sx;
719 cimpl.size_y = sy;
720 cimpl.pad_x = ox;
721 cimpl.pad_y = oy;
723 // some state variables used to determine what has happened
724 bool realtime_drawn = false;
725 bool cache_drawn = false;
726 bool grid_drawn = false;
728 int drawing_phase;
730 // check if we can skip the whole drawing stuff and go on with
731 // copying everything we drawed before
733 if (!lg->layers)
734 goto finalize;
737 // 
741 if ( lg->force_cache
742 or lg->force_redraw
743 or lg->layers & LG_CACHE_GRID
744 or lg->layers & LG_CACHE_GRAPH
745 or lg->layers & LG_CACHE_DOT) {
746 if (lg->debug) printf("\n->cache\n");
748 // someone needs a redraw of the cache so start with the cache
749 // phase
750 drawing_phase = 0;
752 // set the right context to work with
753 _ctx = cache_c;
755 // and switch to grid surface in case we want to draw on it
756 if (lg->debug) printf("switch to grid\n");
757 ctx = calf_line_graph_switch_context(lg, grid_c, &cimpl);
758 } else {
759 if (lg->debug) printf("\n->realtime\n");
761 // no cache drawing neccessary, so skip the first drawing phase
762 drawing_phase = 1;
764 // set the right context to work with
765 _ctx = realtime_c;
767 // and switch to the realtime surface
768 if (lg->debug) printf("switch to realtime\n");
769 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
774 for (int phase = drawing_phase; phase < 2; phase++) {
775 // draw elements on the realtime and/or the cache surface
777 if (lg->debug) printf("\n### drawing phase %d\n", phase);
779 ///////////////////////////////////////////////////////////////
780 // GRID
781 ///////////////////////////////////////////////////////////////
783 if ((lg->layers & LG_CACHE_GRID and !phase) || (lg->layers & LG_REALTIME_GRID and phase)) {
784 // The plugin can set "vertical" to 1
785 // to force drawing of vertical lines instead of horizontal ones
786 // (which is the default)
787 // size and color of the grid (which can be set by the plugin
788 // via the context) are reset for every line.
789 if (!phase) {
790 // we're in cache phase and it seems we really want to
791 // draw new grid lines. so "clear" the grid surface
792 // with a pure background
793 if (lg->debug) printf("copy bg->grid\n");
794 calf_line_graph_copy_surface(ctx, lg->background_surface);
795 grid_drawn = true;
797 for (int a = 0;
798 legend = std::string(),
799 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.66),
800 cairo_set_line_width(ctx, grid_width),
801 lg->source->get_gridline(lg->source_id, a, phase, pos, vertical, legend, &cimpl);
802 a++)
804 if (!a and lg->debug) printf("(draw grid)\n");
805 calf_line_graph_draw_grid( lg, ctx, legend, vertical, pos );
808 if (!phase) {
809 // we're in cache phase so we have to switch back to
810 // the cache surface after drawing the grid on its surface
811 if (lg->debug) printf("switch to cache\n");
812 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
813 // if a grid was drawn copy it to cache
814 if (grid_drawn) {
815 if (lg->debug) printf("copy grid->cache\n");
816 calf_line_graph_copy_surface(ctx, lg->grid_surface);
817 cache_drawn = true;
821 ///////////////////////////////////////////////////////////////
822 // GRAPHS
823 ///////////////////////////////////////////////////////////////
825 if ((lg->layers & LG_CACHE_GRAPH and !phase) || (lg->layers & LG_REALTIME_GRAPH and phase)) {
826 // Cycle through all graphs and hand over the amount of horizontal
827 // pixels. The plugin is expected to set all corresponding vertical
828 // values in an array.
829 // size and color of the graph (which can be set by the plugin
830 // via the context) are reset for every graph.
832 if (!phase) {
833 // we are drawing the first graph in cache phase, so
834 // prepare the cache surface with the grid surface
835 if (lg->debug) printf("copy grid->cache\n");
836 calf_line_graph_copy_surface(ctx, lg->grid_surface);
837 cache_drawn = true;
838 } else if (!realtime_drawn) {
839 // we're in realtime phase and the realtime surface wasn't
840 // reset to cache by now (because there was no cache
841 // phase and no realtime grid was drawn)
842 // so "clear" the realtime surface with the cache
843 if (lg->debug) printf("copy cache->realtime\n");
844 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
845 realtime_drawn = true;
848 for(int a = 0;
849 lg->mode = 0,
850 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.8),
851 cairo_set_line_width(ctx, graph_width),
852 lg->source->get_graph(lg->source_id, a, phase, data, lg->size_x, &cimpl, &lg->mode);
853 a++)
855 if (lg->debug) printf("graph %d\n", a);
856 calf_line_graph_draw_graph( lg, ctx, data, lg->mode );
860 ///////////////////////////////////////////////////////////////
861 // MOVING
862 ///////////////////////////////////////////////////////////////
864 if ((lg->layers & LG_CACHE_MOVING and !phase) || (lg->layers & LG_REALTIME_MOVING and phase)) {
865 // we have a moving curve. switch to moving surface and
866 // clear it before we start to draw
867 if (lg->debug) printf("switch to moving %d\n", lg->movesurf);
868 ctx = calf_line_graph_switch_context(lg, moving_c[lg->movesurf], &cimpl);
869 calf_line_graph_clear_surface(ctx);
871 if (!phase and !cache_drawn) {
872 // we are drawing the first moving in cache phase and
873 // no cache has been created by now, so
874 // prepare the cache surface with the grid surface
875 if (lg->debug) printf("copy grid->cache\n");
876 calf_line_graph_copy_surface(cache_c, lg->grid_surface);
877 cache_drawn = true;
878 } else if (phase and !realtime_drawn) {
879 // we're in realtime phase and the realtime surface wasn't
880 // reset to cache by now (because there was no cache
881 // phase and no realtime grid was drawn)
882 // so "clear" the realtime surface with the cache
883 if (lg->debug) printf("copy cache->realtime\n");
884 calf_line_graph_copy_surface(realtime_c, lg->cache_surface);
885 realtime_drawn = true;
888 int a;
889 int offset;
890 int move = 0;
891 uint32_t color;
892 for(a = 0;
893 offset = a,
894 color = RGBAtoINT(0.35, 0.4, 0.2, 1),
895 lg->source->get_moving(lg->source_id, a, direction, data, lg->size_x, lg->size_y, offset, color);
896 a++)
898 if (lg->debug) printf("moving %d\n", a);
899 calf_line_graph_draw_moving(lg, ctx, data, direction, offset, color);
900 move += offset;
902 move ++;
903 // set moving distances according to direction
904 int x = 0;
905 int y = 0;
906 switch (direction) {
907 case LG_MOVING_LEFT:
908 default:
909 x = -move;
910 y = 0;
911 break;
912 case LG_MOVING_RIGHT:
913 x = move;
914 y = 0;
915 break;
916 case LG_MOVING_UP:
917 x = 0;
918 y = -move;
919 break;
920 case LG_MOVING_DOWN:
921 x = 0;
922 y = move;
923 break;
925 // copy the old moving surface to the right position on the
926 // new surface
927 if (lg->debug) printf("copy cached moving->moving\n");
928 cairo_set_source_surface(ctx, lg->moving_surface[(int)!lg->movesurf], x, y);
929 cairo_paint(ctx);
931 // switch back to the actual context
932 if (lg->debug) printf("switch to realtime/cache\n");
933 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
935 if (lg->debug) printf("copy moving->realtime/cache\n");
936 calf_line_graph_copy_surface(ctx, lg->moving_surface[lg->movesurf], 1);
938 // toggle the moving cache
939 lg->movesurf = (int)!lg->movesurf;
942 ///////////////////////////////////////////////////////////////
943 // DOTS
944 ///////////////////////////////////////////////////////////////
946 if ((lg->layers & LG_CACHE_DOT and !phase) || (lg->layers & LG_REALTIME_DOT and phase)) {
947 // Cycle through all dots. The plugin is expected to set the x
948 // and y value of the dot.
949 // color of the dot (which can be set by the plugin
950 // via the context) is reset for every graph.
952 if (!cache_drawn and !phase) {
953 // we are drawing dots in cache phase while
954 // the cache wasn't renewed (no graph was drawn), so
955 // prepare the cache surface with the grid surface
956 if (lg->debug) printf("copy grid->cache\n");
957 calf_line_graph_copy_surface(ctx, lg->grid_surface);
958 cache_drawn = true;
960 if (!realtime_drawn and phase) {
961 // we're in realtime phase and the realtime surface wasn't
962 // reset to cache by now (because there was no cache
963 // phase and no realtime grid or graph was drawn)
964 // so "clear" the realtime surface with the cache
965 if (lg->debug) printf("copy cache->realtime\n");
966 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
967 realtime_drawn = true;
969 for (int a = 0;
970 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 1),
971 cairo_set_line_width(ctx, dot_width),
972 lg->source->get_dot(lg->source_id, a, phase, x, y, size = 3, &cimpl);
973 a++)
975 if (lg->debug) printf("dot %d\n", a);
976 float yv = oy + sy / 2 - (sy / 2 - 1) * y;
977 cairo_arc(ctx, ox + x * sx, yv, size, 0, 2 * M_PI);
978 cairo_fill(ctx);
982 if (!phase) {
983 // if we have a second cycle for drawing on the realtime
984 // after the cache was renewed it's time to copy the
985 // cache to the realtime and switch the target surface
986 if (lg->debug) printf("switch to realtime\n");
987 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
988 _ctx = realtime_c;
990 if (cache_drawn) {
991 // copy the cache to the realtime if it was changed
992 if (lg->debug) printf("copy cache->realtime\n");
993 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
994 realtime_drawn = true;
995 } else if (grid_drawn) {
996 // copy the grid to the realtime if it was changed
997 if (lg->debug) printf("copy grid->realtime\n");
998 calf_line_graph_copy_surface(ctx, lg->grid_surface, lg->force_cache ? 1 : lg->fade);
999 realtime_drawn = true;
1002 // check if we can skip the whole realtime phase
1003 if (!(lg->layers & LG_REALTIME_GRID)
1004 and !(lg->layers & LG_REALTIME_GRAPH)
1005 and !(lg->layers & LG_REALTIME_DOT)) {
1006 phase = 2;
1009 } // one or two cycles for drawing cached and non-cached elements
1012 // îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚
1015 finalize:
1016 delete[] data;
1017 if (lg->debug) printf("\n### finalize\n");
1019 // whatever happened - we need to copy the realtime surface to the
1020 // window surface
1021 //if (lg->debug) printf("switch to window\n");
1022 //ctx = calf_line_graph_switch_context(lg, c, &cimpl);
1023 if (lg->debug) printf("copy realtime->window\n");
1024 calf_line_graph_copy_surface(c, lg->realtime_surface);
1026 // if someone changed the handles via drag'n'drop or externally we
1027 // need a redraw of the handles surface
1028 if (lg->freqhandles and (lg->handle_redraw or lg->force_redraw)) {
1029 cairo_t *hs = cairo_create(lg->handles_surface);
1030 calf_line_graph_clear_surface(hs);
1031 calf_line_graph_draw_freqhandles(lg, hs);
1032 cairo_destroy(hs);
1035 // if we're using frequency handles we need to copy them to the
1036 // window
1037 if (lg->freqhandles) {
1038 if (lg->debug) printf("copy handles->window\n");
1039 calf_line_graph_copy_surface(c, lg->handles_surface);
1042 // and draw the crosshairs on top if neccessary
1043 if (lg->use_crosshairs && lg->crosshairs_active && lg->mouse_x > 0
1044 && lg->mouse_y > 0 && lg->handle_grabbed < 0) {
1045 std::string s;
1046 s = lg->source->get_crosshair_label((int)(lg->mouse_x - ox), (int)(lg->mouse_y - oy), sx, sy, &cimpl);
1047 cairo_set_line_width(c, 1),
1048 calf_line_graph_draw_crosshairs(lg, c, false, 0, 0.5, 5, false, lg->mouse_x - ox, lg->mouse_y - oy, s);
1051 lg->force_cache = false;
1052 lg->force_redraw = false;
1053 lg->handle_redraw = 0;
1054 lg->recreate_surfaces = 0;
1055 lg->layers = 0;
1057 // destroy all temporarily created cairo contexts
1058 cairo_destroy(c);
1059 cairo_destroy(realtime_c);
1060 cairo_destroy(grid_c);
1061 cairo_destroy(cache_c);
1062 cairo_destroy(moving_c[0]);
1063 cairo_destroy(moving_c[1]);
1065 lg->generation += 1;
1067 return TRUE;
1070 static int
1071 calf_line_graph_get_handle_at(CalfLineGraph *lg, double x, double y)
1073 int sx = lg->size_x;
1074 int sy = lg->size_y;
1075 int ox = lg->pad_x;
1076 int oy = lg->pad_y;
1078 sx += sx % 2 - 1;
1079 sy += sy % 2 - 1;
1081 // loop on all handles
1082 for (int i = 0; i < lg->freqhandles; i++) {
1083 FreqHandle *handle = &lg->freq_handles[i];
1084 if (!handle->is_active())
1085 continue;
1087 if (handle->dimensions == 1) {
1088 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1089 if (lg->mouse_x <= ox + round(handle->value_x * sx + HANDLE_WIDTH / 2.0) + 0.5 &&
1090 lg->mouse_x >= ox + round(handle->value_x * sx - HANDLE_WIDTH / 2.0) - 0.5 ) {
1091 return i;
1093 } else if (handle->dimensions >= 2) {
1094 double dx = lg->mouse_x - round(ox + handle->value_x * sx);
1095 double dy = lg->mouse_y - round(oy + handle->value_y * sy);
1097 // if mouse clicked inside circle of HANDLE_WIDTH
1098 if (sqrt(dx * dx + dy * dy) <= HANDLE_WIDTH / 2.0)
1099 return i;
1102 return -1;
1105 static gboolean
1106 calf_line_graph_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
1108 g_assert(CALF_IS_LINE_GRAPH(widget));
1109 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1111 int sx = lg->size_x;
1112 int sy = lg->size_y;
1113 int ox = lg->pad_x;
1114 int oy = lg->pad_y;
1116 sx += sx % 2 - 1;
1117 sy += sy % 2 - 1;
1119 lg->mouse_x = event->x;
1120 lg->mouse_y = event->y;
1122 if (lg->handle_grabbed >= 0) {
1123 FreqHandle *handle = &lg->freq_handles[lg->handle_grabbed];
1125 float new_x_value = float(event->x - ox) / float(sx);
1126 float new_y_value = float(event->y - oy) / float(sy);
1128 if (new_x_value < handle->left_bound) {
1129 new_x_value = handle->left_bound;
1130 } else if (new_x_value > handle->right_bound) {
1131 new_x_value = handle->right_bound;
1134 // restrict y range by top and bottom
1135 if (handle->dimensions >= 2) {
1136 if(new_y_value < 0.0) new_y_value = 0.0;
1137 if(new_y_value > 1.0) new_y_value = 1.0;
1140 if (new_x_value != handle->value_x ||
1141 new_y_value != handle->value_y) {
1142 handle->value_x = new_x_value;
1143 handle->value_y = new_y_value;
1145 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1147 lg->handle_redraw = 1;
1148 calf_line_graph_expose_request(widget, true);
1150 if (event->is_hint)
1152 gdk_event_request_motions(event);
1155 int handle_hovered = calf_line_graph_get_handle_at(lg, event->x, event->y);
1156 if (handle_hovered != lg->handle_hovered) {
1157 if (lg->handle_grabbed >= 0 ||
1158 handle_hovered != -1) {
1159 gdk_window_set_cursor(widget->window, lg->hand_cursor);
1160 lg->handle_hovered = handle_hovered;
1161 } else {
1162 gdk_window_set_cursor(widget->window, lg->arrow_cursor);
1163 lg->handle_hovered = -1;
1165 lg->handle_redraw = 1;
1166 calf_line_graph_expose_request(widget, true);
1168 if(lg->crosshairs_active and lg->use_crosshairs) {
1169 calf_line_graph_expose_request(widget, true);
1171 return TRUE;
1174 static gboolean
1175 calf_line_graph_button_press (GtkWidget *widget, GdkEventButton *event)
1177 g_assert(CALF_IS_LINE_GRAPH(widget));
1178 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1179 bool inside_handle = false;
1181 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1182 if (i != -1)
1184 FreqHandle *handle = &lg->freq_handles[i];
1186 if (handle->dimensions == 1) {
1187 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1188 lg->handle_grabbed = i;
1189 inside_handle = true;
1191 if (lg->enforce_handle_order) {
1192 // look for previous one dimensional handle to find left_bound
1193 for (int j = i - 1; j >= 0; j--) {
1194 FreqHandle *prevhandle = &lg->freq_handles[j];
1195 if(prevhandle->is_active() && prevhandle->dimensions == 1) {
1196 handle->left_bound = prevhandle->value_x + lg->min_handle_distance;
1197 break;
1201 // look for next one dimensional handle to find right_bound
1202 for (int j = i + 1; j < lg->freqhandles; j++) {
1203 FreqHandle *nexthandle = &lg->freq_handles[j];
1204 if(nexthandle->is_active() && nexthandle->dimensions == 1) {
1205 handle->right_bound = nexthandle->value_x - lg->min_handle_distance;
1206 break;
1210 } else if (handle->dimensions >= 2) {
1211 lg->handle_grabbed = i;
1212 inside_handle = true;
1216 if (inside_handle && event->type == GDK_2BUTTON_PRESS) {
1217 FreqHandle &handle = lg->freq_handles[lg->handle_grabbed];
1218 handle.value_x = handle.default_value_x;
1219 handle.value_y = handle.default_value_y;
1220 g_signal_emit_by_name(widget, "freqhandle-changed", &handle);
1223 if(!inside_handle) {
1224 lg->crosshairs_active = !lg->crosshairs_active;
1227 calf_line_graph_expose_request(widget, true);
1228 gtk_widget_grab_focus(widget);
1229 gtk_grab_add(widget);
1231 return TRUE;
1234 static gboolean
1235 calf_line_graph_button_release (GtkWidget *widget, GdkEventButton *event)
1237 g_assert(CALF_IS_LINE_GRAPH(widget));
1238 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1240 lg->handle_grabbed = -1;
1242 if (GTK_WIDGET_HAS_GRAB(widget))
1243 gtk_grab_remove(widget);
1245 calf_line_graph_expose_request(widget, true);
1246 return TRUE;
1249 static gboolean
1250 calf_line_graph_scroll (GtkWidget *widget, GdkEventScroll *event)
1252 g_assert(CALF_IS_LINE_GRAPH(widget));
1253 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1255 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1256 if (i != -1)
1258 FreqHandle *handle = &lg->freq_handles[i];
1259 if (handle->dimensions == 3) {
1260 if (event->direction == GDK_SCROLL_UP) {
1261 handle->value_z += 0.05;
1262 if(handle->value_z > 1.0) {
1263 handle->value_z = 1.0;
1265 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1266 } else if (event->direction == GDK_SCROLL_DOWN) {
1267 handle->value_z -= 0.05;
1268 if(handle->value_z < 0.0) {
1269 handle->value_z = 0.0;
1271 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1273 lg->handle_redraw = 1;
1276 return TRUE;
1279 static gboolean
1280 calf_line_graph_enter (GtkWidget *widget, GdkEventCrossing *event)
1282 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1284 if (lg->debug) printf("[enter]\n");
1285 return TRUE;
1288 static gboolean
1289 calf_line_graph_leave (GtkWidget *widget, GdkEventCrossing *event)
1292 g_assert(CALF_IS_LINE_GRAPH(widget));
1293 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1295 if (lg->debug) printf("[leave]\n");
1296 if (lg->mouse_x >= 0 or lg->mouse_y >= 0)
1297 calf_line_graph_expose_request(widget, true);
1298 lg->mouse_x = -1;
1299 lg->mouse_y = -1;
1301 return TRUE;
1305 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
1307 g_assert(CALF_IS_LINE_GRAPH(graph));
1308 graph->is_square = is_square;
1311 static void
1312 calf_line_graph_size_request (GtkWidget *widget,
1313 GtkRequisition *requisition)
1315 g_assert(CALF_IS_LINE_GRAPH(widget));
1317 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1319 if (lg->debug) printf("size request\n");
1322 static void
1323 calf_line_graph_size_allocate (GtkWidget *widget,
1324 GtkAllocation *allocation)
1326 g_assert(CALF_IS_LINE_GRAPH(widget));
1327 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1329 if (lg->debug) printf("size allocation\n");
1331 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
1333 // remember the allocation
1334 widget->allocation = *allocation;
1336 // reset the allocation if a square widget is requested
1337 GtkAllocation &a = widget->allocation;
1338 if (lg->is_square)
1340 if (a.width > a.height)
1342 a.x += (a.width - a.height) / 2;
1343 a.width = a.height;
1345 if (a.width < a.height)
1347 a.y += (a.height - a.width) / 2;
1348 a.height = a.width;
1352 lg->size_x = a.width - lg->pad_x * 2;
1353 lg->size_y = a.height - lg->pad_y * 2;
1355 lg->recreate_surfaces = 1;
1356 parent_class->size_allocate( widget, &a );
1360 static void
1361 calf_line_graph_class_init (CalfLineGraphClass *klass)
1363 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1364 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1365 widget_class->expose_event = calf_line_graph_expose;
1366 widget_class->size_request = calf_line_graph_size_request;
1367 widget_class->size_allocate = calf_line_graph_size_allocate;
1368 widget_class->button_press_event = calf_line_graph_button_press;
1369 widget_class->button_release_event = calf_line_graph_button_release;
1370 widget_class->motion_notify_event = calf_line_graph_pointer_motion;
1371 widget_class->scroll_event = calf_line_graph_scroll;
1372 widget_class->enter_notify_event = calf_line_graph_enter;
1373 widget_class->leave_notify_event = calf_line_graph_leave;
1375 g_signal_new("freqhandle-changed",
1376 G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST,
1377 0, NULL, NULL,
1378 g_cclosure_marshal_VOID__POINTER,
1379 G_TYPE_NONE, 1, G_TYPE_POINTER);
1382 static void
1383 calf_line_graph_unrealize (GtkWidget *widget, CalfLineGraph *lg)
1385 if (lg->debug) printf("unrealize\n");
1386 calf_line_graph_destroy_surfaces(widget);
1389 static void
1390 calf_line_graph_init (CalfLineGraph *lg)
1392 GtkWidget *widget = GTK_WIDGET(lg);
1394 if (lg->debug) printf("lg init\n");
1396 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS | GTK_SENSITIVE | GTK_PARENT_SENSITIVE);
1397 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);
1399 widget->requisition.width = 40;
1400 widget->requisition.height = 40;
1401 lg->force_cache = true;
1402 lg->force_redraw = false;
1403 lg->zoom = 1;
1404 lg->param_zoom = -1;
1405 lg->offset = 0;
1406 lg->param_offset = -1;
1407 lg->recreate_surfaces = 1;
1408 lg->mode = 0;
1409 lg->movesurf = 0;
1410 lg->generation = 0;
1411 lg->arrow_cursor = gdk_cursor_new(GDK_LEFT_PTR);
1412 lg->hand_cursor = gdk_cursor_new(GDK_FLEUR);
1413 lg->layers = LG_CACHE_GRID | LG_CACHE_GRAPH
1414 | LG_CACHE_DOT | LG_CACHE_MOVING
1415 | LG_REALTIME_GRID | LG_REALTIME_GRAPH
1416 | LG_REALTIME_DOT | LG_REALTIME_MOVING;
1418 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_line_graph_unrealize), (gpointer)lg);
1420 for(int i = 0; i < FREQ_HANDLES; i++) {
1421 FreqHandle *handle = &lg->freq_handles[i];
1422 handle->active = false;
1423 handle->param_active_no = -1;
1424 handle->param_x_no = -1;
1425 handle->param_y_no = -1;
1426 handle->value_x = -1.0;
1427 handle->value_y = -1.0;
1428 handle->param_x_no = -1;
1429 handle->label = NULL;
1430 handle->left_bound = 0.0 + lg->min_handle_distance;
1431 handle->right_bound = 1.0 - lg->min_handle_distance;
1434 lg->handle_grabbed = -1;
1435 lg->handle_hovered = -1;
1436 lg->handle_redraw = 1;
1437 lg->min_handle_distance = 0.025;
1439 lg->background_surface = NULL;
1440 lg->grid_surface = NULL;
1441 lg->cache_surface = NULL;
1442 lg->moving_surface[0] = NULL;
1443 lg->moving_surface[1] = NULL;
1444 lg->handles_surface = NULL;
1445 lg->realtime_surface = NULL;
1448 GtkWidget *
1449 calf_line_graph_new()
1451 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
1454 GType
1455 calf_line_graph_get_type (void)
1457 static GType type = 0;
1458 if (!type) {
1459 static const GTypeInfo type_info = {
1460 sizeof(CalfLineGraphClass),
1461 NULL, /* base_init */
1462 NULL, /* base_finalize */
1463 (GClassInitFunc)calf_line_graph_class_init,
1464 NULL, /* class_finalize */
1465 NULL, /* class_data */
1466 sizeof(CalfLineGraph),
1467 0, /* n_preallocs */
1468 (GInstanceInitFunc)calf_line_graph_init
1471 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
1473 for (int i = 0; ; i++) {
1474 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
1475 if (g_type_from_name(name)) {
1476 free(name);
1477 continue;
1479 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
1480 name,
1481 type_info_copy,
1482 (GTypeFlags)0);
1483 free(name);
1484 break;
1487 return type;