EQ8/EQ12: High pass and low pass with Q
[calf.git] / src / ctl_linegraph.cpp
blobefe6511e8f34ab026771ef60522d66bf6c44e337
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 std;
41 using namespace calf_plugins;
43 static void
44 calf_line_graph_draw_grid( CalfLineGraph* lg, cairo_t *ctx, string &legend, bool vertical, float pos )
46 int sx = lg->size_x;
47 int sy = lg->size_y;
48 int ox = lg->pad_x;
49 int oy = lg->pad_y;
51 float x = 0, y = 0;
53 cairo_text_extents_t tx;
54 int size;
55 if (!legend.empty()) {
56 cairo_text_extents(ctx, legend.c_str(), &tx);
57 size = vertical ? tx.height : tx.width;
58 size += 5;
59 } else {
60 size = 0;
63 if (vertical)
65 x = floor(ox + pos * sx) + 0.5;
66 cairo_move_to(ctx, x, oy);
67 cairo_line_to(ctx, x, oy + sy - size);
68 cairo_stroke(ctx);
69 if (!legend.empty()) {
70 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
71 cairo_move_to(ctx, x - (tx.x_bearing + tx.width / 2.0), oy + sy - 2);
72 cairo_show_text(ctx, legend.c_str());
75 else
77 y = floor(oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5;
78 cairo_move_to(ctx, ox, y);
79 cairo_line_to(ctx, ox + sx - size, y);
80 cairo_stroke(ctx);
82 if (!legend.empty()) {
83 cairo_set_source_rgba(ctx, 0.0, 0.0, 0.0, 0.5);
84 cairo_move_to(ctx, ox + sx - 4 - tx.width, y + tx.height/2 - 2);
85 cairo_show_text(ctx, legend.c_str());
88 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());
91 static int
92 calf_line_graph_draw_graph( CalfLineGraph* lg, cairo_t *ctx, float *data, int mode = 0 )
94 if (lg->debug) printf("(draw graph)\n");
96 int sx = lg->size_x;
97 int sy = lg->size_y;
98 int ox = lg->pad_x;
99 int oy = lg->pad_y;
101 int _lastx = 0;
102 float y = 0.f;
103 int startdraw = -1;
105 for (int i = 0; i < sx; i++) {
106 y = (oy + sy / 2 - (sy / 2 - 1) * data[i]);
107 if (lg->debug > 2) printf("* graph x: %d, y: %.5f, data: %.5f\n", i, y, data[i]);
108 switch (mode) {
109 case 0:
110 case 1:
111 default:
112 // we want to draw a line
113 if (i and (data[i] < INFINITY or i == sx - 1)) {
114 cairo_line_to(ctx, ox + i, y);
115 } else if (i and startdraw >= 0) {
116 continue;
117 } else {
118 cairo_move_to(ctx, ox, y);
119 if (startdraw < 0)
120 startdraw = i;
122 break;
123 case 2:
124 // bars are used
125 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
126 cairo_rectangle(ctx, ox + _lastx, (int)y, i - _lastx, sy - (int)y + oy);
127 _lastx = i;
128 if (startdraw < 0)
129 startdraw = ox + _lastx;
130 } else {
131 continue;
133 break;
134 case 3:
135 // this one is drawing little boxes at the values position
136 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
137 cairo_rectangle(ctx, ox + _lastx, (int)y - 1, i - _lastx, 2);
138 _lastx = i;
139 if (startdraw < 0)
140 startdraw = ox + _lastx;
141 } else {
142 continue;
144 break;
145 case 4:
146 // this one is drawing bars centered on the x axis
147 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
148 cairo_rectangle(ctx, ox + _lastx, oy + sy / 2, i - _lastx, -1 * data[i] * (sy / 2));
149 _lastx = i;
150 if (startdraw < 0)
151 startdraw = ox + _lastx;
152 } else {
153 continue;
155 break;
156 case 5:
157 // this one is drawing bars centered on the x axis with 1
158 // as the center
159 if (i and ((data[i] < INFINITY) or i == sx - 1)) {
160 cairo_rectangle(ctx, ox + _lastx,oy + sy / 2 - sy * lg->offset / 2, i - _lastx, -1 * data[i] * (sy / 2) + sy * lg->offset / 2);
161 _lastx = i;
162 if (startdraw < 0)
163 startdraw = ox + _lastx;
164 } else {
165 continue;
167 break;
170 if (mode == 1) {
171 cairo_line_to(ctx, sx + 2 * ox, sy + 2 * oy);
172 cairo_line_to(ctx, 0, sy + 2 * oy);
173 cairo_close_path(ctx);
175 if(!mode) {
176 cairo_stroke(ctx);
177 } else {
178 cairo_fill(ctx);
180 return startdraw;
183 static void
184 calf_line_graph_draw_moving(CalfLineGraph* lg, cairo_t *ctx, float *data, int direction, int offset, int color)
186 if (lg->debug) printf("(draw moving)\n");
188 int sx = lg->size_x;
189 int sy = lg->size_y;
190 int ox = lg->pad_x;
191 int oy = lg->pad_y;
193 int _last = 0;
194 int startdraw = -1;
196 int sm = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? sx : sy);
197 int om = (direction == LG_MOVING_UP || direction == LG_MOVING_DOWN ? ox : oy);
198 for (int i = 0; i < sm; i++) {
199 if (lg->debug > 2) printf("* moving i: %d, dir: %d, offset: %d, data: %.5f\n", i, direction, offset, data[i]);
200 if (i and ((data[i] < INFINITY) or i >= sm)) {
201 cairo_set_source_rgba(ctx, INTtoR(color), INTtoG(color), INTtoB(color), (data[i] + 1) / 1.4 * INTtoA(color));
202 switch (direction) {
203 case LG_MOVING_LEFT:
204 default:
205 cairo_rectangle(ctx, ox + sx - 1 - offset, oy + _last, 1, i - _last);
206 break;
207 case LG_MOVING_RIGHT:
208 cairo_rectangle(ctx, ox + offset, oy + _last, 1, i - _last);
209 break;
210 case LG_MOVING_UP:
211 cairo_rectangle(ctx, ox + _last, oy + sy - 1 - offset, i - _last, 1);
212 break;
213 case LG_MOVING_DOWN:
214 cairo_rectangle(ctx, ox + _last, oy + offset, i - _last, 1);
215 break;
218 cairo_fill(ctx);
219 _last = i;
220 if (startdraw < 0)
221 startdraw = om + _last;
223 else
224 continue;
229 void calf_line_graph_draw_label(CalfLineGraph * lg, cairo_t *cache_cr, string label, int x, int y, double bgopac)
231 int hmarg = 8;
232 int linepad = 4;
233 int bgpad = 4;
234 if (label.empty())
235 return;
236 cairo_text_extents_t tx;
237 int n = int(std::count(label.begin(), label.end(), '\n')) + 1;
238 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.5);
239 double h = 0;
240 double w = 0;
241 double z = 0;
242 string::size_type lpos = label.find_first_not_of("\n", 0);
243 string::size_type pos = label.find_first_of("\n", lpos);
244 while (string::npos != pos || string::npos != lpos) {
245 string str = label.substr(lpos, pos - lpos);
246 cairo_text_extents(cache_cr, str.c_str(), &tx);
247 h += tx.height + linepad;
248 w = std::max(w, tx.width);
249 z = tx.height + linepad;
250 lpos = label.find_first_not_of("\n", pos);
251 pos = label.find_first_of("\n", lpos);
253 cairo_save(cache_cr);
255 // set bgopac to > 1 if the background should be drawn without
256 // clipping to the labels dimensions
257 if (bgopac < 1) {
258 cairo_rectangle(cache_cr, x - hmarg - w - 2 * bgpad,
259 y - 3 - int(n / 2) * z - bgpad,
260 w + 2 * bgpad,
261 h + 2 * bgpad);
262 cairo_clip(cache_cr);
263 } else {
264 bgopac -= 1;
266 cairo_set_source_surface(cache_cr, lg->background_surface, 0, 0);
267 cairo_paint_with_alpha(cache_cr, bgopac);
268 cairo_restore(cache_cr);
269 int p = 0;
270 lpos = label.find_first_not_of("\n", 0);
271 pos = label.find_first_of("\n", lpos);
272 while (string::npos != pos || string::npos != lpos) {
273 string str = label.substr(lpos, pos - lpos);
274 cairo_text_extents(cache_cr, str.c_str(), &tx);
275 if (!p)
276 p = y - 3 - (n / 2) * (tx.height + linepad);
277 p += tx.height + linepad;
278 cairo_move_to(cache_cr, x - hmarg - tx.width - bgpad, p);
279 cairo_show_text(cache_cr, str.c_str());
280 lpos = label.find_first_not_of("\n", pos);
281 pos = label.find_first_of("\n", lpos);
285 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, string label, double label_bg)
287 if (lg->debug) printf("(draw crosshairs)\n");
288 // crosshairs
290 int sx = lg->size_x;
291 int sy = lg->size_y;
292 int ox = lg->pad_x;
293 int oy = lg->pad_y;
295 int _x = ox + x;
296 int _y = ox + y;
298 calf_line_graph_draw_label(lg, cache_cr, label, x - mask, y, label_bg);
300 cairo_pattern_t *pat;
302 if(mask > 0 and circle) {
303 cairo_move_to(cache_cr, _x, _y);
304 cairo_arc (cache_cr, _x, _y, mask, 0, 2 * M_PI);
305 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
306 cairo_fill(cache_cr);
307 if (alpha < 0.3) {
308 cairo_move_to(cache_cr, _x, _y);
309 cairo_arc (cache_cr, _x, _y, HANDLE_WIDTH / 2, 0, 2 * M_PI);
310 cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.2);
311 cairo_fill(cache_cr);
314 if(gradient and gradient_rad > 0) {
315 // draw the crosshairs with a steady gradient around
316 pat = cairo_pattern_create_radial(_x, _y, 1, _x, _y, gradient_rad * 2);
317 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
318 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
319 // top
320 cairo_rectangle(cache_cr, _x, _y - gradient_rad, 1, gradient_rad - mask);
321 // right
322 cairo_rectangle(cache_cr, _x + mask, _y, gradient_rad - mask, 1);
323 // bottom
324 cairo_rectangle(cache_cr, _x, _y + mask, 1, gradient_rad - mask);
325 // left
326 cairo_rectangle(cache_cr, _x - gradient_rad, _y, gradient_rad - mask, 1);
328 cairo_set_source(cache_cr, pat);
329 cairo_fill(cache_cr);
330 } else if(gradient) {
331 // draw the crosshairs with a gradient to the frame
332 // top
333 cairo_rectangle(cache_cr, _x, oy, 1, y - mask);
334 pat = cairo_pattern_create_linear(_x, oy, _x, _y);
335 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
336 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
337 cairo_set_source(cache_cr, pat);
338 cairo_fill(cache_cr);
339 // right
340 cairo_rectangle(cache_cr, _x + mask, _y, sx - x - mask, 1);
341 pat = cairo_pattern_create_linear(_x, oy, sx, oy);
342 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
343 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
344 cairo_set_source(cache_cr, pat);
345 cairo_fill(cache_cr);
346 // bottom
347 cairo_rectangle(cache_cr, _x, _y + mask, 1, sy - y - mask);
348 pat = cairo_pattern_create_linear(_x, _y, _x, oy + sy);
349 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, alpha);
350 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, 0);
351 cairo_set_source(cache_cr, pat);
352 cairo_fill(cache_cr);
353 // left
354 cairo_rectangle(cache_cr, ox, _y, x - mask, 1);
355 pat = cairo_pattern_create_linear(ox, oy, _x, oy);
356 cairo_pattern_add_color_stop_rgba(pat, 0, 0, 0, 0, 0);
357 cairo_pattern_add_color_stop_rgba(pat, 1, 0, 0, 0, alpha);
358 cairo_set_source(cache_cr, pat);
359 cairo_fill(cache_cr);
360 } else {
361 // draw normal crosshairs
362 // top
363 cairo_move_to(cache_cr, _x + 0.5, oy + 0.5);
364 cairo_line_to(cache_cr, _x + 0.5, _y - mask + 0.5);
365 // right
366 cairo_move_to(cache_cr, _x + mask + 0.5, _y + 0.5);
367 cairo_line_to(cache_cr, ox + sx + 0.5, _y + 0.5);
368 // bottom
369 cairo_move_to(cache_cr, _x + 0.5, _y + mask + 0.5);
370 cairo_line_to(cache_cr, _x + 0.5, oy + sy + 0.5);
371 // left
372 cairo_move_to(cache_cr, ox + 0.5, _y + 0.5);
373 cairo_line_to(cache_cr, _x - mask + 0.5, _y + 0.5);
375 cairo_set_source_rgba(cache_cr, 0, 0, 0, alpha);
376 cairo_stroke(cache_cr);
380 void calf_line_graph_draw_freqhandles(CalfLineGraph* lg, cairo_t* c)
382 // freq_handles
383 if (lg->debug) printf("(draw handles)\n");
385 int sx = lg->size_x;
386 int sy = lg->size_y;
387 int ox = lg->pad_x;
388 int oy = lg->pad_y;
390 if (lg->freqhandles > 0) {
391 cairo_set_source_rgba(c, 0.0, 0.0, 0.0, 1.0);
392 cairo_set_line_width(c, 1.0);
394 for (int i = 0; i < lg->freqhandles; i++) {
395 FreqHandle *handle = &lg->freq_handles[i];
397 if(!handle->is_active() or handle->value_x < 0.0 or handle->value_x > 1.0)
398 continue;
400 int val_x = round(handle->value_x * sx);
401 int val_y = (handle->dimensions >= 2) ? round(handle->value_y * sy) : 0;
402 float pat_alpha;
403 bool grad;
404 char label[1024];
405 float freq = exp((handle->value_x) * log(1000)) * 20.0;
407 // choose colors between dragged and normal state
408 if (lg->handle_hovered == i) {
409 pat_alpha = 0.3;
410 grad = false;
411 cairo_set_source_rgba(c, 0, 0, 0, 0.7);
412 } else {
413 pat_alpha = 0.1;
414 grad = true;
415 //cairo_set_source_rgb(c, 0.44, 0.5, 0.21);
416 cairo_set_source_rgba(c, 0, 0, 0, 0.5);
418 if (handle->dimensions >= 2) {
419 cairo_move_to(c, val_x + 8, val_y);
420 } else {
421 cairo_move_to(c, val_x + 11, oy + 15);
424 if (handle->dimensions == 1) {
425 // draw the main line
426 cairo_move_to(c, ox + val_x + 0.5, oy);
427 cairo_line_to(c, ox + val_x + 0.5, oy + sy);
428 cairo_stroke(c);
429 // draw some one-dimensional bling-bling
430 cairo_pattern_t *pat;
431 switch(handle->style) {
432 default:
433 case 0:
434 // bell filters, default
435 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
436 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
437 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha);
438 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
439 cairo_rectangle(c, ox + val_x - 7, oy, 6, sy);
440 cairo_rectangle(c, ox + val_x + 2, oy, 6, sy);
441 break;
442 case 1:
443 // hipass
444 pat = cairo_pattern_create_linear(ox, oy, ox + val_x, oy);
445 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
446 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, pat_alpha);
447 cairo_rectangle(c, ox, oy, val_x - 1, sy);
448 break;
449 case 2:
450 // loshelf
451 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
452 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
453 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
454 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
455 cairo_rectangle(c, ox, oy, val_x - 1, sy);
456 break;
457 case 3:
458 // hishelf
459 pat = cairo_pattern_create_linear(ox, oy, ox, sy);
460 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, 0);
461 cairo_pattern_add_color_stop_rgba(pat, 0.5, 0, 0, 0, pat_alpha * 1.5);
462 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
463 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 2, sy);
464 break;
465 case 4:
466 // lopass
467 pat = cairo_pattern_create_linear(ox + val_x, oy, ox + sx, oy);
468 cairo_pattern_add_color_stop_rgba(pat, 0.f, 0, 0, 0, pat_alpha);
469 cairo_pattern_add_color_stop_rgba(pat, 1.f, 0, 0, 0, 0);
470 cairo_rectangle(c, ox + val_x + 2, oy, sx - val_x - 1, sy);
471 break;
473 cairo_set_source(c, pat);
474 cairo_fill(c);
475 cairo_pattern_destroy(pat);
476 if (handle->label && strlen(handle->label))
477 sprintf(label, "%.2f Hz\n%s", freq, handle->label);
478 else
479 sprintf(label, "%.2f Hz", freq);
480 calf_line_graph_draw_label(lg, c, label, val_x, oy + 15, 0.5);
481 } else {
482 string tmp;
483 int mask = 30 - log10(1 + handle->value_z * 9) * 30 + HANDLE_WIDTH / 2.f;
484 if (lg->handle_hovered == i)
485 tmp = calf_plugins::frequency_crosshair_label(val_x, val_y, sx, sy, 1, 1, 1, 1, lg->zoom * 128, 0);
486 else
487 tmp = calf_plugins::frequency_crosshair_label(val_x, val_y, sx, sy, 1, 0, 0, 0, lg->zoom * 128, 0);
488 if (handle->label && strlen(handle->label))
489 sprintf(label, "%s\n%s", handle->label, tmp.c_str());
490 else
491 strcpy(label, tmp.c_str());
492 calf_line_graph_draw_crosshairs(lg, c, grad, -1, pat_alpha, mask, true, val_x, val_y, label, lg->handle_hovered == i ? 0.8 : 0.5);
498 static void
499 calf_line_graph_destroy_surfaces (GtkWidget *widget)
501 g_assert(CALF_IS_LINE_GRAPH(widget));
502 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
504 if (lg->debug) printf("{destroy surfaces}\n");
506 // destroy all surfaces - and don't tell anybody about it - hehe
507 if( lg->background_surface )
508 cairo_surface_destroy( lg->background_surface );
509 if( lg->grid_surface )
510 cairo_surface_destroy( lg->grid_surface );
511 if( lg->cache_surface )
512 cairo_surface_destroy( lg->cache_surface );
513 if( lg->moving_surface[0] )
514 cairo_surface_destroy( lg->moving_surface[0] );
515 if( lg->moving_surface[1] )
516 cairo_surface_destroy( lg->moving_surface[1] );
517 if( lg->handles_surface )
518 cairo_surface_destroy( lg->handles_surface );
519 if( lg->realtime_surface )
520 cairo_surface_destroy( lg->realtime_surface );
522 static void
523 calf_line_graph_create_surfaces (GtkWidget *widget)
525 g_assert(CALF_IS_LINE_GRAPH(widget));
526 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
528 if (lg->debug) printf("{create surfaces}\n");
530 int width = widget->allocation.width;
531 int height = widget->allocation.height;
533 // the size of the "real" drawing area
534 lg->size_x = width - lg->pad_x * 2;
535 lg->size_y = height - lg->pad_y * 2;
537 calf_line_graph_destroy_surfaces(widget);
538 // create the background surface.
539 // background holds the graphics of the frame and the yellowish
540 // background light for faster redrawing of static stuff
541 lg->background_surface = cairo_image_surface_create(
542 CAIRO_FORMAT_ARGB32, width, height );
544 // create the grid surface.
545 // this one is used as a cache for the grid on the background in the
546 // cache phase. If a graph or dot in cache phase needs to be redrawn
547 // we don't need to redraw the whole grid.
548 lg->grid_surface = cairo_image_surface_create(
549 CAIRO_FORMAT_ARGB32, width, height );
551 // create the cache surface.
552 // cache holds a copy of the background with a static part of
553 // the grid and some static curves and dots.
554 lg->cache_surface = cairo_image_surface_create(
555 CAIRO_FORMAT_ARGB32, width, height );
557 // create the moving surface.
558 // moving is used as a cache for any slowly moving graphics like
559 // spectralizer or waveforms
560 lg->moving_surface[0] = cairo_image_surface_create(
561 CAIRO_FORMAT_ARGB32, width, height );
563 // create the moving temp surface.
564 // moving is used as a cache for any slowly moving graphics like
565 // spectralizer or waveforms
566 lg->moving_surface[1] = cairo_image_surface_create(
567 CAIRO_FORMAT_ARGB32, width, height );
569 // create the handles surface.
570 // this one contains the handles graphics to avoid redrawing
571 // each cycle
572 lg->handles_surface = cairo_image_surface_create(
573 CAIRO_FORMAT_ARGB32, width, height );
575 // create the realtime surface.
576 // realtime is used to cache the realtime graphics for drawing the
577 // crosshairs on top if nothing else changed
578 lg->realtime_surface = cairo_image_surface_create(
579 CAIRO_FORMAT_ARGB32, width, height );
581 lg->force_cache = true;
584 static cairo_t
585 *calf_line_graph_switch_context(CalfLineGraph* lg, cairo_t *ctx, cairo_impl *cimpl)
587 if (lg->debug) printf("{switch context}\n");
589 cimpl->context = ctx;
590 cairo_select_font_face(ctx, "Sans",
591 CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
592 cairo_set_font_size(ctx, 9);
593 cairo_set_line_join(ctx, CAIRO_LINE_JOIN_MITER);
594 cairo_rectangle(ctx, lg->pad_x, lg->pad_y, lg->size_x, lg->size_y);
595 cairo_clip(ctx);
596 return ctx;
599 static void
600 calf_line_graph_copy_surface(cairo_t *ctx, cairo_surface_t *source, int x = 0, int y = 0, float fade = 1.f)
602 // copy a surface to a cairo context
603 cairo_save(ctx);
604 cairo_set_source_surface(ctx, source, x, y);
605 if (fade < 1.0) {
606 cairo_paint_with_alpha(ctx, fade * 0.35 + 0.05);
607 } else {
608 cairo_paint(ctx);
610 cairo_restore(ctx);
613 static void
614 calf_line_graph_clear_surface(cairo_t *ctx)
616 // clears a surface to transparent
617 cairo_save (ctx);
618 cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
619 cairo_paint (ctx);
620 cairo_restore (ctx);
623 void calf_line_graph_expose_request (GtkWidget *widget, bool force)
625 // someone thinks we should redraw the line graph. let's see what
626 // the plugin thinks about. To do that a bitmask is sent to the
627 // plugin which can be changed. If the plugin returns true or if
628 // the request is in response of something like dragged handles, an
629 // exposition of the widget is requested from GTK
631 g_assert(CALF_IS_LINE_GRAPH(widget));
632 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
634 // quit if no source available
635 if (!lg->source) return;
637 if (lg->debug > 1) printf("\n\n### expose request %d ###\n", lg->generation);
639 // let a bitmask be switched by the plugin to determine the layers
640 // we want to draw. We set all cache layers to true if force_cache
641 // is set otherwise default is to draw nothing. The return value
642 // tells us whether the plugin wants to draw at all or not.
643 lg->layers = 0;
644 //if (lg->force_cache || lg->recreate_surfaces)
645 //lg->layers |= LG_CACHE_GRID | LG_CACHE_GRAPH | LG_CACHE_DOT | LG_CACHE_MOVING;
647 //if (lg->debug > 1) {
648 //printf("bitmask ");
649 //dsp::print_bits(sizeof(lg->layers), &lg->layers);
650 //printf("\n");
653 // if plugin returns true (something has obviously changed) or if
654 // the requestor forces a redraw, request an exposition of the widget
655 // from GTK
656 if (lg->source->get_layers(lg->source_id, lg->generation, lg->layers) or force)
657 gtk_widget_queue_draw(widget);
660 static gboolean
661 calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
663 g_assert(CALF_IS_LINE_GRAPH(widget));
664 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
666 // quit if no source available
667 if (!lg->source) return FALSE;
669 if (lg->debug) printf("\n\n####### exposing %d #######\n", lg->generation);
671 // cairo context of the window
672 cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
675 // recreate surfaces if someone needs it (init of the widget,
676 // resizing the window..)
677 if (lg->recreate_surfaces) {
678 lg->pad_x = widget->style->xthickness;
679 lg->pad_y = widget->style->ythickness;
680 lg->x = widget->allocation.x;
681 lg->y = widget->allocation.y;
682 float radius, bevel;
683 gtk_widget_style_get(widget, "border-radius", &radius, "bevel", &bevel, NULL);
685 if (lg->debug) printf("recreation...\n");
686 calf_line_graph_create_surfaces(widget);
688 // all surfaces were recreated, so background is empty.
689 // draw the yellowish lighting on the background surface
690 cairo_t *bg = cairo_create(lg->background_surface);
691 if (lg->debug) printf("(draw background)\n");
692 display_background(widget, bg, 0, 0, lg->size_x, lg->size_y, lg->pad_x, lg->pad_y, radius, bevel);
693 cairo_destroy(bg);
696 // the cache, grid and realtime surface wrapped in a cairo context
697 cairo_t *grid_c = cairo_create( lg->grid_surface );
698 cairo_t *cache_c = cairo_create( lg->cache_surface );
699 cairo_t *realtime_c = cairo_create( lg->realtime_surface );
701 if (lg->recreate_surfaces) {
702 // and copy it to the grid surface in case no grid is drawn
703 if (lg->debug) printf("copy bg->grid\n");
704 calf_line_graph_copy_surface(grid_c, lg->background_surface);
706 // and copy it to the cache surface in case no cache is drawn
707 if (lg->debug) printf("copy bg->cache\n");
708 calf_line_graph_copy_surface(cache_c, lg->background_surface);
710 // and copy it to the realtime surface in case no realtime is drawn
711 if (lg->debug) printf("copy bg->realtime\n");
712 calf_line_graph_copy_surface(realtime_c, lg->background_surface);
714 if (lg->recreate_surfaces or lg->force_redraw) {
715 // reset generation value and request a new expose event
716 lg->generation = 0;
717 lg->source->get_layers(lg->source_id, lg->generation, lg->layers);
720 int sx = lg->size_x;
721 int sy = lg->size_y;
722 int ox = lg->pad_x;
723 int oy = lg->pad_y;
725 if (lg->debug) printf("width: %d height: %d x: %d y: %d\n", sx, sy, ox, oy);
727 if (lg->debug) {
728 printf("bitmask ");
729 dsp::print_bits(sizeof(lg->layers), &lg->layers);
730 printf("\n");
733 // context used for the actual surface we want to draw on. It is
734 // switched over the drawing process via calf_line_graph_switch_context
735 cairo_t *ctx = NULL;
736 cairo_t *_ctx = NULL;
738 // the contexts for both moving curve caches
739 cairo_t *moving_c[2];
740 moving_c[0] = cairo_create( lg->moving_surface[0] );
741 moving_c[1] = cairo_create( lg->moving_surface[1] );
743 // the line widths to switch to between cycles
744 float grid_width = 1.0;
745 float graph_width = 1.5;
746 float dot_width = 0.0;
748 // more vars we have to initialize, mainly stuff we use in callback
749 // functions
750 float *data = new float[2 * std::max(lg->size_x, lg->size_y)];
751 float pos = 0;
752 bool vertical = false;
753 string legend = "";
754 int size = 0;
755 int direction = 0;
756 float x, y;
758 // a cairo wrapper to hand over contexts to the plugin for setting
759 // line colors, widths aso
760 cairo_impl cimpl;
761 cimpl.size_x = sx;
762 cimpl.size_y = sy;
763 cimpl.pad_x = ox;
764 cimpl.pad_y = oy;
766 // some state variables used to determine what has happened
767 bool realtime_drawn = false;
768 bool cache_drawn = false;
769 bool grid_drawn = false;
771 int drawing_phase;
773 // check if we can skip the whole drawing stuff and go on with
774 // copying everything we drawed before
776 if (!lg->layers)
777 goto finalize;
780 // 
784 if ( lg->force_cache
785 or lg->force_redraw
786 or lg->layers & LG_CACHE_GRID
787 or lg->layers & LG_CACHE_GRAPH
788 or lg->layers & LG_CACHE_DOT) {
789 if (lg->debug) printf("\n->cache\n");
791 // someone needs a redraw of the cache so start with the cache
792 // phase
793 drawing_phase = 0;
795 // set the right context to work with
796 _ctx = cache_c;
798 // and switch to grid surface in case we want to draw on it
799 if (lg->debug) printf("switch to grid\n");
800 ctx = calf_line_graph_switch_context(lg, grid_c, &cimpl);
801 } else {
802 if (lg->debug) printf("\n->realtime\n");
804 // no cache drawing neccessary, so skip the first drawing phase
805 drawing_phase = 1;
807 // set the right context to work with
808 _ctx = realtime_c;
810 // and switch to the realtime surface
811 if (lg->debug) printf("switch to realtime\n");
812 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
817 for (int phase = drawing_phase; phase < 2; phase++) {
818 // draw elements on the realtime and/or the cache surface
820 if (lg->debug) printf("\n### drawing phase %d\n", phase);
822 ///////////////////////////////////////////////////////////////
823 // GRID
824 ///////////////////////////////////////////////////////////////
826 if ((lg->layers & LG_CACHE_GRID and !phase) || (lg->layers & LG_REALTIME_GRID and phase)) {
827 // The plugin can set "vertical" to 1
828 // to force drawing of vertical lines instead of horizontal ones
829 // (which is the default)
830 // size and color of the grid (which can be set by the plugin
831 // via the context) are reset for every line.
832 if (!phase) {
833 // we're in cache phase and it seems we really want to
834 // draw new grid lines. so "clear" the grid surface
835 // with a pure background
836 if (lg->debug) printf("copy bg->grid\n");
837 calf_line_graph_copy_surface(ctx, lg->background_surface);
838 grid_drawn = true;
840 for (int a = 0;
841 legend = string(),
842 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.66),
843 cairo_set_line_width(ctx, grid_width),
844 lg->source->get_gridline(lg->source_id, a, phase, pos, vertical, legend, &cimpl);
845 a++)
847 if (!a and lg->debug) printf("(draw grid)\n");
848 calf_line_graph_draw_grid( lg, ctx, legend, vertical, pos );
851 if (!phase) {
852 // we're in cache phase so we have to switch back to
853 // the cache surface after drawing the grid on its surface
854 if (lg->debug) printf("switch to cache\n");
855 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
856 // if a grid was drawn copy it to cache
857 if (grid_drawn) {
858 if (lg->debug) printf("copy grid->cache\n");
859 calf_line_graph_copy_surface(ctx, lg->grid_surface);
860 cache_drawn = true;
864 ///////////////////////////////////////////////////////////////
865 // GRAPHS
866 ///////////////////////////////////////////////////////////////
868 if ((lg->layers & LG_CACHE_GRAPH and !phase) || (lg->layers & LG_REALTIME_GRAPH and phase)) {
869 // Cycle through all graphs and hand over the amount of horizontal
870 // pixels. The plugin is expected to set all corresponding vertical
871 // values in an array.
872 // size and color of the graph (which can be set by the plugin
873 // via the context) are reset for every graph.
875 if (!phase) {
876 // we are drawing the first graph in cache phase, so
877 // prepare the cache surface with the grid surface
878 if (lg->debug) printf("copy grid->cache\n");
879 calf_line_graph_copy_surface(ctx, lg->grid_surface);
880 cache_drawn = true;
881 } else if (!realtime_drawn) {
882 // we're in realtime phase and the realtime surface wasn't
883 // reset to cache by now (because there was no cache
884 // phase and no realtime grid was drawn)
885 // so "clear" the realtime surface with the cache
886 if (lg->debug) printf("copy cache->realtime\n");
887 calf_line_graph_copy_surface(ctx, lg->cache_surface, 0, 0, lg->force_cache ? 1 : lg->fade);
888 realtime_drawn = true;
891 for(int a = 0;
892 lg->mode = 0,
893 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.8),
894 cairo_set_line_width(ctx, graph_width),
895 lg->source->get_graph(lg->source_id, a, phase, data, lg->size_x, &cimpl, &lg->mode);
896 a++)
898 if (lg->debug) printf("graph %d\n", a);
899 calf_line_graph_draw_graph( lg, ctx, data, lg->mode );
903 ///////////////////////////////////////////////////////////////
904 // MOVING
905 ///////////////////////////////////////////////////////////////
907 if ((lg->layers & LG_CACHE_MOVING and !phase) || (lg->layers & LG_REALTIME_MOVING and phase)) {
908 // we have a moving curve. switch to moving surface and
909 // clear it before we start to draw
910 if (lg->debug) printf("switch to moving %d\n", lg->movesurf);
911 ctx = calf_line_graph_switch_context(lg, moving_c[lg->movesurf], &cimpl);
912 calf_line_graph_clear_surface(ctx);
914 if (!phase and !cache_drawn) {
915 // we are drawing the first moving in cache phase and
916 // no cache has been created by now, so
917 // prepare the cache surface with the grid surface
918 if (lg->debug) printf("copy grid->cache\n");
919 calf_line_graph_copy_surface(cache_c, lg->grid_surface);
920 cache_drawn = true;
921 } else if (phase and !realtime_drawn) {
922 // we're in realtime phase and the realtime surface wasn't
923 // reset to cache by now (because there was no cache
924 // phase and no realtime grid was drawn)
925 // so "clear" the realtime surface with the cache
926 if (lg->debug) printf("copy cache->realtime\n");
927 calf_line_graph_copy_surface(realtime_c, lg->cache_surface);
928 realtime_drawn = true;
931 int a;
932 int offset;
933 int move = 0;
934 uint32_t color;
935 for(a = 0;
936 offset = a,
937 color = RGBAtoINT(0.35, 0.4, 0.2, 1),
938 lg->source->get_moving(lg->source_id, a, direction, data, lg->size_x, lg->size_y, offset, color);
939 a++)
941 if (lg->debug) printf("moving %d\n", a);
942 calf_line_graph_draw_moving(lg, ctx, data, direction, offset, color);
943 move += offset;
945 move ++;
946 // set moving distances according to direction
947 int xd = 0;
948 int yd = 0;
949 switch (direction) {
950 case LG_MOVING_LEFT:
951 default:
952 xd = -move;
953 yd = 0;
954 break;
955 case LG_MOVING_RIGHT:
956 xd = move;
957 yd = 0;
958 break;
959 case LG_MOVING_UP:
960 xd = 0;
961 yd = -move;
962 break;
963 case LG_MOVING_DOWN:
964 xd = 0;
965 yd = move;
966 break;
968 // copy the old moving surface to the right position on the
969 // new surface
970 if (lg->debug) printf("copy cached moving->moving\n");
971 cairo_set_source_surface(ctx, lg->moving_surface[(int)!lg->movesurf], xd, yd);
972 cairo_paint(ctx);
974 // switch back to the actual context
975 if (lg->debug) printf("switch to realtime/cache\n");
976 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
978 if (lg->debug) printf("copy moving->realtime/cache\n");
979 calf_line_graph_copy_surface(ctx, lg->moving_surface[lg->movesurf]);
981 // toggle the moving cache
982 lg->movesurf = (int)!lg->movesurf;
985 ///////////////////////////////////////////////////////////////
986 // DOTS
987 ///////////////////////////////////////////////////////////////
989 if ((lg->layers & LG_CACHE_DOT and !phase) || (lg->layers & LG_REALTIME_DOT and phase)) {
990 // Cycle through all dots. The plugin is expected to set the x
991 // and y value of the dot.
992 // color of the dot (which can be set by the plugin
993 // via the context) is reset for every graph.
995 if (!cache_drawn and !phase) {
996 // we are drawing dots in cache phase while
997 // the cache wasn't renewed (no graph was drawn), so
998 // prepare the cache surface with the grid surface
999 if (lg->debug) printf("copy grid->cache\n");
1000 calf_line_graph_copy_surface(ctx, lg->grid_surface);
1001 cache_drawn = true;
1003 if (!realtime_drawn and phase) {
1004 // we're in realtime phase and the realtime surface wasn't
1005 // reset to cache by now (because there was no cache
1006 // phase and no realtime grid or graph was drawn)
1007 // so "clear" the realtime surface with the cache
1008 if (lg->debug) printf("copy cache->realtime\n");
1009 calf_line_graph_copy_surface(ctx, lg->cache_surface, 0, 0, lg->force_cache ? 1 : lg->fade);
1010 realtime_drawn = true;
1012 for (int a = 0;
1013 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 1),
1014 cairo_set_line_width(ctx, dot_width),
1015 lg->source->get_dot(lg->source_id, a, phase, x, y, size = 3, &cimpl);
1016 a++)
1018 if (lg->debug) printf("dot %d\n", a);
1019 float yv = oy + sy / 2 - (sy / 2 - 1) * y;
1020 cairo_arc(ctx, ox + x * sx, yv, size, 0, 2 * M_PI);
1021 cairo_fill(ctx);
1025 if (!phase) {
1026 // if we have a second cycle for drawing on the realtime
1027 // after the cache was renewed it's time to copy the
1028 // cache to the realtime and switch the target surface
1029 if (lg->debug) printf("switch to realtime\n");
1030 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
1031 _ctx = realtime_c;
1033 if (cache_drawn) {
1034 // copy the cache to the realtime if it was changed
1035 if (lg->debug) printf("copy cache->realtime\n");
1036 calf_line_graph_copy_surface(ctx, lg->cache_surface, 0, 0, lg->force_cache ? 1 : lg->fade);
1037 realtime_drawn = true;
1038 } else if (grid_drawn) {
1039 // copy the grid to the realtime if it was changed
1040 if (lg->debug) printf("copy grid->realtime\n");
1041 calf_line_graph_copy_surface(ctx, lg->grid_surface, 0, 0, lg->force_cache ? 1 : lg->fade);
1042 realtime_drawn = true;
1045 // check if we can skip the whole realtime phase
1046 if (!(lg->layers & LG_REALTIME_GRID)
1047 and !(lg->layers & LG_REALTIME_GRAPH)
1048 and !(lg->layers & LG_REALTIME_DOT)) {
1049 phase = 2;
1052 } // one or two cycles for drawing cached and non-cached elements
1055 // îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚
1058 finalize:
1059 delete[] data;
1060 if (lg->debug) printf("\n### finalize\n");
1062 // whatever happened - we need to copy the realtime surface to the
1063 // window surface
1064 //if (lg->debug) printf("switch to window\n");
1065 //ctx = calf_line_graph_switch_context(lg, c, &cimpl);
1066 if (lg->debug) printf("copy realtime->window\n");
1067 calf_line_graph_copy_surface(c, lg->realtime_surface, lg->x, lg->y);
1069 // if someone changed the handles via drag'n'drop or externally we
1070 // need a redraw of the handles surface
1071 if (lg->freqhandles and (lg->handle_redraw or lg->force_redraw)) {
1072 cairo_t *hs = cairo_create(lg->handles_surface);
1073 calf_line_graph_clear_surface(hs);
1074 calf_line_graph_draw_freqhandles(lg, hs);
1075 cairo_destroy(hs);
1078 // if we're using frequency handles we need to copy them to the
1079 // window
1080 if (lg->freqhandles) {
1081 if (lg->debug) printf("copy handles->window\n");
1082 calf_line_graph_copy_surface(c, lg->handles_surface, lg->x, lg->y);
1085 // and draw the crosshairs on top if neccessary
1086 if (lg->use_crosshairs && lg->crosshairs_active && lg->mouse_x > 0
1087 && lg->mouse_y > 0 && lg->handle_grabbed < 0) {
1088 string s;
1089 s = lg->source->get_crosshair_label((int)(lg->mouse_x - ox), (int)(lg->mouse_y - oy), sx, sy, 1, 1, 1, 1);
1090 cairo_set_line_width(c, 1),
1091 calf_line_graph_draw_crosshairs(lg, c, false, 0, 0.5, 5, false, lg->mouse_x - ox, lg->mouse_y - oy, s, 1.5);
1094 lg->force_cache = false;
1095 lg->force_redraw = false;
1096 lg->handle_redraw = 0;
1097 lg->recreate_surfaces = 0;
1098 lg->layers = 0;
1100 // destroy all temporarily created cairo contexts
1101 cairo_destroy(c);
1102 cairo_destroy(realtime_c);
1103 cairo_destroy(grid_c);
1104 cairo_destroy(cache_c);
1105 cairo_destroy(moving_c[0]);
1106 cairo_destroy(moving_c[1]);
1108 lg->generation += 1;
1110 return TRUE;
1113 static int
1114 calf_line_graph_get_handle_at(CalfLineGraph *lg, double x, double y)
1116 int sx = lg->size_x;
1117 int sy = lg->size_y;
1118 int ox = lg->pad_x;
1119 int oy = lg->pad_y;
1121 sx += sx % 2 - 1;
1122 sy += sy % 2 - 1;
1124 // loop on all handles
1125 for (int i = 0; i < lg->freqhandles; i++) {
1126 FreqHandle *handle = &lg->freq_handles[i];
1127 if (!handle->is_active())
1128 continue;
1130 if (handle->dimensions == 1) {
1131 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1132 if (lg->mouse_x <= ox + round(handle->value_x * sx + HANDLE_WIDTH / 2.0) + 0.5 &&
1133 lg->mouse_x >= ox + round(handle->value_x * sx - HANDLE_WIDTH / 2.0) - 0.5 ) {
1134 return i;
1136 } else if (handle->dimensions >= 2) {
1137 double dx = lg->mouse_x - round(ox + handle->value_x * sx);
1138 double dy = lg->mouse_y - round(oy + handle->value_y * sy);
1140 // if mouse clicked inside circle of HANDLE_WIDTH
1141 if (sqrt(dx * dx + dy * dy) <= HANDLE_WIDTH / 2.0)
1142 return i;
1145 return -1;
1148 static gboolean
1149 calf_line_graph_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
1151 g_assert(CALF_IS_LINE_GRAPH(widget));
1152 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1154 int sx = lg->size_x;
1155 int sy = lg->size_y;
1156 int ox = lg->pad_x;
1157 int oy = lg->pad_y;
1159 sx += sx % 2 - 1;
1160 sy += sy % 2 - 1;
1162 lg->mouse_x = event->x;
1163 lg->mouse_y = event->y;
1165 if (lg->handle_grabbed >= 0) {
1166 FreqHandle *handle = &lg->freq_handles[lg->handle_grabbed];
1168 float new_x_value = float(lg->mouse_x - ox) / float(sx);
1169 float new_y_value = float(lg->mouse_y - oy) / float(sy);
1171 if (new_x_value < handle->left_bound) {
1172 new_x_value = handle->left_bound;
1173 } else if (new_x_value > handle->right_bound) {
1174 new_x_value = handle->right_bound;
1177 // restrict y range by top and bottom
1178 if (handle->dimensions >= 2) {
1179 if(new_y_value < 0.0) new_y_value = 0.0;
1180 if(new_y_value > 1.0) new_y_value = 1.0;
1183 if (new_x_value != handle->value_x ||
1184 new_y_value != handle->value_y) {
1185 handle->value_x = new_x_value;
1186 handle->value_y = new_y_value;
1188 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1190 lg->handle_redraw = 1;
1191 calf_line_graph_expose_request(widget, true);
1193 if (event->is_hint)
1195 gdk_event_request_motions(event);
1198 int handle_hovered = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1199 if (handle_hovered != lg->handle_hovered) {
1200 if (lg->handle_grabbed >= 0 ||
1201 handle_hovered != -1) {
1202 gdk_window_set_cursor(widget->window, lg->hand_cursor);
1203 lg->handle_hovered = handle_hovered;
1204 } else {
1205 gdk_window_set_cursor(widget->window, lg->arrow_cursor);
1206 lg->handle_hovered = -1;
1208 lg->handle_redraw = 1;
1209 calf_line_graph_expose_request(widget, true);
1211 if(lg->crosshairs_active and lg->use_crosshairs) {
1212 calf_line_graph_expose_request(widget, true);
1214 return TRUE;
1217 static gboolean
1218 calf_line_graph_button_press (GtkWidget *widget, GdkEventButton *event)
1220 g_assert(CALF_IS_LINE_GRAPH(widget));
1221 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1222 bool inside_handle = false;
1224 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1225 if (i != -1)
1227 FreqHandle *handle = &lg->freq_handles[i];
1229 if (handle->dimensions == 1) {
1230 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1231 lg->handle_grabbed = i;
1232 inside_handle = true;
1234 if (lg->enforce_handle_order) {
1235 // look for previous one dimensional handle to find left_bound
1236 for (int j = i - 1; j >= 0; j--) {
1237 FreqHandle *prevhandle = &lg->freq_handles[j];
1238 if(prevhandle->is_active() && prevhandle->dimensions == 1) {
1239 handle->left_bound = prevhandle->value_x + lg->min_handle_distance;
1240 break;
1244 // look for next one dimensional handle to find right_bound
1245 for (int j = i + 1; j < lg->freqhandles; j++) {
1246 FreqHandle *nexthandle = &lg->freq_handles[j];
1247 if(nexthandle->is_active() && nexthandle->dimensions == 1) {
1248 handle->right_bound = nexthandle->value_x - lg->min_handle_distance;
1249 break;
1253 } else if (handle->dimensions >= 2) {
1254 lg->handle_grabbed = i;
1255 inside_handle = true;
1259 if (inside_handle && event->type == GDK_2BUTTON_PRESS) {
1260 FreqHandle &handle = lg->freq_handles[lg->handle_grabbed];
1261 handle.value_x = handle.default_value_x;
1262 handle.value_y = handle.default_value_y;
1263 g_signal_emit_by_name(widget, "freqhandle-changed", &handle);
1266 if(!inside_handle) {
1267 lg->crosshairs_active = !lg->crosshairs_active;
1270 calf_line_graph_expose_request(widget, true);
1271 gtk_widget_grab_focus(widget);
1272 gtk_grab_add(widget);
1274 return TRUE;
1277 static gboolean
1278 calf_line_graph_button_release (GtkWidget *widget, GdkEventButton *event)
1280 g_assert(CALF_IS_LINE_GRAPH(widget));
1281 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1283 lg->handle_grabbed = -1;
1285 if (GTK_WIDGET_HAS_GRAB(widget))
1286 gtk_grab_remove(widget);
1288 calf_line_graph_expose_request(widget, true);
1289 return TRUE;
1292 static gboolean
1293 calf_line_graph_scroll (GtkWidget *widget, GdkEventScroll *event)
1295 g_assert(CALF_IS_LINE_GRAPH(widget));
1296 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1298 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1299 if (i != -1)
1301 FreqHandle *handle = &lg->freq_handles[i];
1302 if (handle->dimensions == 3) {
1303 if (event->direction == GDK_SCROLL_UP) {
1304 handle->value_z += 0.05;
1305 if(handle->value_z > 1.0) {
1306 handle->value_z = 1.0;
1308 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1309 } else if (event->direction == GDK_SCROLL_DOWN) {
1310 handle->value_z -= 0.05;
1311 if(handle->value_z < 0.0) {
1312 handle->value_z = 0.0;
1314 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1316 lg->handle_redraw = 1;
1319 return TRUE;
1322 static gboolean
1323 calf_line_graph_enter (GtkWidget *widget, GdkEventCrossing *event)
1325 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1327 if (lg->debug) printf("[enter]\n");
1328 return TRUE;
1331 static gboolean
1332 calf_line_graph_leave (GtkWidget *widget, GdkEventCrossing *event)
1335 g_assert(CALF_IS_LINE_GRAPH(widget));
1336 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1338 if (lg->debug) printf("[leave]\n");
1339 if (lg->mouse_x >= 0 or lg->mouse_y >= 0)
1340 calf_line_graph_expose_request(widget, true);
1341 lg->mouse_x = -1;
1342 lg->mouse_y = -1;
1344 return TRUE;
1348 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
1350 g_assert(CALF_IS_LINE_GRAPH(graph));
1351 graph->is_square = is_square;
1354 static void
1355 calf_line_graph_size_request (GtkWidget *widget,
1356 GtkRequisition *requisition)
1358 g_assert(CALF_IS_LINE_GRAPH(widget));
1360 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1362 if (lg->debug) printf("size request\n");
1365 static void
1366 calf_line_graph_size_allocate (GtkWidget *widget,
1367 GtkAllocation *allocation)
1369 g_assert(CALF_IS_LINE_GRAPH(widget));
1370 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1372 if (lg->debug) printf("size allocation\n");
1374 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
1376 // remember the allocation
1377 widget->allocation = *allocation;
1379 // reset the allocation if a square widget is requested
1380 GtkAllocation &a = widget->allocation;
1381 if (lg->is_square)
1383 if (a.width > a.height)
1385 a.x += (a.width - a.height) / 2;
1386 a.width = a.height;
1388 if (a.width < a.height)
1390 a.y += (a.height - a.width) / 2;
1391 a.height = a.width;
1395 lg->size_x = a.width - lg->pad_x * 2;
1396 lg->size_y = a.height - lg->pad_y * 2;
1398 lg->recreate_surfaces = 1;
1399 parent_class->size_allocate( widget, &a );
1403 static void
1404 calf_line_graph_class_init (CalfLineGraphClass *klass)
1406 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1407 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1408 widget_class->expose_event = calf_line_graph_expose;
1409 widget_class->size_request = calf_line_graph_size_request;
1410 widget_class->size_allocate = calf_line_graph_size_allocate;
1411 widget_class->button_press_event = calf_line_graph_button_press;
1412 widget_class->button_release_event = calf_line_graph_button_release;
1413 widget_class->motion_notify_event = calf_line_graph_pointer_motion;
1414 widget_class->scroll_event = calf_line_graph_scroll;
1415 widget_class->enter_notify_event = calf_line_graph_enter;
1416 widget_class->leave_notify_event = calf_line_graph_leave;
1417 gtk_widget_class_install_style_property(
1418 widget_class, g_param_spec_float("border-radius", "Border Radius", "Generate round edges",
1419 0, 24, 4, GParamFlags(G_PARAM_READWRITE)));
1420 gtk_widget_class_install_style_property(
1421 widget_class, g_param_spec_float("bevel", "Bevel", "Bevel the object",
1422 -2, 2, 0.2, GParamFlags(G_PARAM_READWRITE)));
1424 g_signal_new("freqhandle-changed",
1425 G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST,
1426 0, NULL, NULL,
1427 g_cclosure_marshal_VOID__POINTER,
1428 G_TYPE_NONE, 1, G_TYPE_POINTER);
1431 static void
1432 calf_line_graph_unrealize (GtkWidget *widget, CalfLineGraph *lg)
1434 if (lg->debug) printf("unrealize\n");
1435 calf_line_graph_destroy_surfaces(widget);
1438 static void
1439 calf_line_graph_init (CalfLineGraph *lg)
1441 GtkWidget *widget = GTK_WIDGET(lg);
1443 if (lg->debug) printf("lg init\n");
1445 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS | GTK_SENSITIVE | GTK_PARENT_SENSITIVE);
1446 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);
1448 widget->requisition.width = 40;
1449 widget->requisition.height = 40;
1450 lg->pad_x = widget->style->xthickness;
1451 lg->pad_y = widget->style->ythickness;
1452 lg->force_cache = true;
1453 lg->force_redraw = false;
1454 lg->zoom = 1;
1455 lg->param_zoom = -1;
1456 lg->offset = 0;
1457 lg->param_offset = -1;
1458 lg->recreate_surfaces = 1;
1459 lg->mode = 0;
1460 lg->movesurf = 0;
1461 lg->generation = 0;
1462 lg->arrow_cursor = gdk_cursor_new(GDK_LEFT_PTR);
1463 lg->hand_cursor = gdk_cursor_new(GDK_FLEUR);
1464 lg->layers = LG_CACHE_GRID | LG_CACHE_GRAPH
1465 | LG_CACHE_DOT | LG_CACHE_MOVING
1466 | LG_REALTIME_GRID | LG_REALTIME_GRAPH
1467 | LG_REALTIME_DOT | LG_REALTIME_MOVING;
1469 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_line_graph_unrealize), (gpointer)lg);
1471 for(int i = 0; i < FREQ_HANDLES; i++) {
1472 FreqHandle *handle = &lg->freq_handles[i];
1473 handle->active = false;
1474 handle->param_active_no = -1;
1475 handle->param_x_no = -1;
1476 handle->param_y_no = -1;
1477 handle->value_x = -1.0;
1478 handle->value_y = -1.0;
1479 handle->param_x_no = -1;
1480 handle->label = NULL;
1481 handle->left_bound = 0.0 + lg->min_handle_distance;
1482 handle->right_bound = 1.0 - lg->min_handle_distance;
1485 lg->handle_grabbed = -1;
1486 lg->handle_hovered = -1;
1487 lg->handle_redraw = 1;
1488 lg->min_handle_distance = 0.025;
1490 lg->background_surface = NULL;
1491 lg->grid_surface = NULL;
1492 lg->cache_surface = NULL;
1493 lg->moving_surface[0] = NULL;
1494 lg->moving_surface[1] = NULL;
1495 lg->handles_surface = NULL;
1496 lg->realtime_surface = NULL;
1498 gtk_event_box_set_visible_window(GTK_EVENT_BOX(widget), FALSE);
1499 //gtk_widget_set_has_window(widget, FALSE);
1502 GtkWidget *
1503 calf_line_graph_new()
1505 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
1508 GType
1509 calf_line_graph_get_type (void)
1511 static GType type = 0;
1512 if (!type) {
1513 static const GTypeInfo type_info = {
1514 sizeof(CalfLineGraphClass),
1515 NULL, /* base_init */
1516 NULL, /* base_finalize */
1517 (GClassInitFunc)calf_line_graph_class_init,
1518 NULL, /* class_finalize */
1519 NULL, /* class_data */
1520 sizeof(CalfLineGraph),
1521 0, /* n_preallocs */
1522 (GInstanceInitFunc)calf_line_graph_init
1525 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
1527 for (int i = 0; ; i++) {
1528 //const char *name = "CalfLineGraph";
1529 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
1530 if (g_type_from_name(name)) {
1531 free(name);
1532 continue;
1534 type = g_type_register_static( GTK_TYPE_EVENT_BOX,
1535 name,
1536 type_info_copy,
1537 (GTypeFlags)0);
1538 free(name);
1539 break;
1542 return type;