Merge branch 'design' of https://github.com/calf-studio-gear/calf into design
[calf.git] / src / ctl_linegraph.cpp
blobfcdfdcc22a1203faa9b4967d83b9ef73b1450778
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, float fade = 1.f)
602 // copy a surface to a cairo context
603 cairo_save(ctx);
604 cairo_set_source_surface(ctx, source, 0, 0);
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));
674 // recreate surfaces if someone needs it (init of the widget,
675 // resizing the window..)
676 if (lg->recreate_surfaces) {
677 if (lg->debug) printf("recreation...\n");
678 calf_line_graph_create_surfaces(widget);
680 // all surfaces were recreated, so background is empty.
681 // draw the yellowish lighting on the background surface
682 cairo_t *bg = cairo_create(lg->background_surface);
683 if (lg->debug) printf("(draw background)\n");
684 display_background(widget, bg, 0, 0, lg->size_x, lg->size_y, lg->pad_x, lg->pad_y);
685 cairo_destroy(bg);
688 // the cache, grid and realtime surface wrapped in a cairo context
689 cairo_t *grid_c = cairo_create( lg->grid_surface );
690 cairo_t *cache_c = cairo_create( lg->cache_surface );
691 cairo_t *realtime_c = cairo_create( lg->realtime_surface );
693 if (lg->recreate_surfaces) {
694 // and copy it to the grid surface in case no grid is drawn
695 if (lg->debug) printf("copy bg->grid\n");
696 calf_line_graph_copy_surface(grid_c, lg->background_surface);
698 // and copy it to the cache surface in case no cache is drawn
699 if (lg->debug) printf("copy bg->cache\n");
700 calf_line_graph_copy_surface(cache_c, lg->background_surface);
702 // and copy it to the realtime surface in case no realtime is drawn
703 if (lg->debug) printf("copy bg->realtime\n");
704 calf_line_graph_copy_surface(realtime_c, lg->background_surface);
706 if (lg->recreate_surfaces or lg->force_redraw) {
707 // reset generation value and request a new expose event
708 lg->generation = 0;
709 lg->source->get_layers(lg->source_id, lg->generation, lg->layers);
712 int sx = lg->size_x;
713 int sy = lg->size_y;
714 int ox = lg->pad_x;
715 int oy = lg->pad_y;
717 if (lg->debug) printf("width: %d height: %d x: %d y: %d\n", sx, sy, ox, oy);
719 if (lg->debug) {
720 printf("bitmask ");
721 dsp::print_bits(sizeof(lg->layers), &lg->layers);
722 printf("\n");
725 // context used for the actual surface we want to draw on. It is
726 // switched over the drawing process via calf_line_graph_switch_context
727 cairo_t *ctx = NULL;
728 cairo_t *_ctx = NULL;
730 // the contexts for both moving curve caches
731 cairo_t *moving_c[2];
732 moving_c[0] = cairo_create( lg->moving_surface[0] );
733 moving_c[1] = cairo_create( lg->moving_surface[1] );
735 // the line widths to switch to between cycles
736 float grid_width = 1.0;
737 float graph_width = 1.5;
738 float dot_width = 0.0;
740 // more vars we have to initialize, mainly stuff we use in callback
741 // functions
742 float *data = new float[2 * std::max(lg->size_x, lg->size_y)];
743 float pos = 0;
744 bool vertical = false;
745 string legend = "";
746 int size = 0;
747 int direction = 0;
748 float x, y;
750 // a cairo wrapper to hand over contexts to the plugin for setting
751 // line colors, widths aso
752 cairo_impl cimpl;
753 cimpl.size_x = sx;
754 cimpl.size_y = sy;
755 cimpl.pad_x = ox;
756 cimpl.pad_y = oy;
758 // some state variables used to determine what has happened
759 bool realtime_drawn = false;
760 bool cache_drawn = false;
761 bool grid_drawn = false;
763 int drawing_phase;
765 // check if we can skip the whole drawing stuff and go on with
766 // copying everything we drawed before
768 if (!lg->layers)
769 goto finalize;
772 // 
776 if ( lg->force_cache
777 or lg->force_redraw
778 or lg->layers & LG_CACHE_GRID
779 or lg->layers & LG_CACHE_GRAPH
780 or lg->layers & LG_CACHE_DOT) {
781 if (lg->debug) printf("\n->cache\n");
783 // someone needs a redraw of the cache so start with the cache
784 // phase
785 drawing_phase = 0;
787 // set the right context to work with
788 _ctx = cache_c;
790 // and switch to grid surface in case we want to draw on it
791 if (lg->debug) printf("switch to grid\n");
792 ctx = calf_line_graph_switch_context(lg, grid_c, &cimpl);
793 } else {
794 if (lg->debug) printf("\n->realtime\n");
796 // no cache drawing neccessary, so skip the first drawing phase
797 drawing_phase = 1;
799 // set the right context to work with
800 _ctx = realtime_c;
802 // and switch to the realtime surface
803 if (lg->debug) printf("switch to realtime\n");
804 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
809 for (int phase = drawing_phase; phase < 2; phase++) {
810 // draw elements on the realtime and/or the cache surface
812 if (lg->debug) printf("\n### drawing phase %d\n", phase);
814 ///////////////////////////////////////////////////////////////
815 // GRID
816 ///////////////////////////////////////////////////////////////
818 if ((lg->layers & LG_CACHE_GRID and !phase) || (lg->layers & LG_REALTIME_GRID and phase)) {
819 // The plugin can set "vertical" to 1
820 // to force drawing of vertical lines instead of horizontal ones
821 // (which is the default)
822 // size and color of the grid (which can be set by the plugin
823 // via the context) are reset for every line.
824 if (!phase) {
825 // we're in cache phase and it seems we really want to
826 // draw new grid lines. so "clear" the grid surface
827 // with a pure background
828 if (lg->debug) printf("copy bg->grid\n");
829 calf_line_graph_copy_surface(ctx, lg->background_surface);
830 grid_drawn = true;
832 for (int a = 0;
833 legend = string(),
834 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.66),
835 cairo_set_line_width(ctx, grid_width),
836 lg->source->get_gridline(lg->source_id, a, phase, pos, vertical, legend, &cimpl);
837 a++)
839 if (!a and lg->debug) printf("(draw grid)\n");
840 calf_line_graph_draw_grid( lg, ctx, legend, vertical, pos );
843 if (!phase) {
844 // we're in cache phase so we have to switch back to
845 // the cache surface after drawing the grid on its surface
846 if (lg->debug) printf("switch to cache\n");
847 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
848 // if a grid was drawn copy it to cache
849 if (grid_drawn) {
850 if (lg->debug) printf("copy grid->cache\n");
851 calf_line_graph_copy_surface(ctx, lg->grid_surface);
852 cache_drawn = true;
856 ///////////////////////////////////////////////////////////////
857 // GRAPHS
858 ///////////////////////////////////////////////////////////////
860 if ((lg->layers & LG_CACHE_GRAPH and !phase) || (lg->layers & LG_REALTIME_GRAPH and phase)) {
861 // Cycle through all graphs and hand over the amount of horizontal
862 // pixels. The plugin is expected to set all corresponding vertical
863 // values in an array.
864 // size and color of the graph (which can be set by the plugin
865 // via the context) are reset for every graph.
867 if (!phase) {
868 // we are drawing the first graph in cache phase, so
869 // prepare the cache surface with the grid surface
870 if (lg->debug) printf("copy grid->cache\n");
871 calf_line_graph_copy_surface(ctx, lg->grid_surface);
872 cache_drawn = true;
873 } else if (!realtime_drawn) {
874 // we're in realtime phase and the realtime surface wasn't
875 // reset to cache by now (because there was no cache
876 // phase and no realtime grid was drawn)
877 // so "clear" the realtime surface with the cache
878 if (lg->debug) printf("copy cache->realtime\n");
879 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
880 realtime_drawn = true;
883 for(int a = 0;
884 lg->mode = 0,
885 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 0.8),
886 cairo_set_line_width(ctx, graph_width),
887 lg->source->get_graph(lg->source_id, a, phase, data, lg->size_x, &cimpl, &lg->mode);
888 a++)
890 if (lg->debug) printf("graph %d\n", a);
891 calf_line_graph_draw_graph( lg, ctx, data, lg->mode );
895 ///////////////////////////////////////////////////////////////
896 // MOVING
897 ///////////////////////////////////////////////////////////////
899 if ((lg->layers & LG_CACHE_MOVING and !phase) || (lg->layers & LG_REALTIME_MOVING and phase)) {
900 // we have a moving curve. switch to moving surface and
901 // clear it before we start to draw
902 if (lg->debug) printf("switch to moving %d\n", lg->movesurf);
903 ctx = calf_line_graph_switch_context(lg, moving_c[lg->movesurf], &cimpl);
904 calf_line_graph_clear_surface(ctx);
906 if (!phase and !cache_drawn) {
907 // we are drawing the first moving in cache phase and
908 // no cache has been created by now, so
909 // prepare the cache surface with the grid surface
910 if (lg->debug) printf("copy grid->cache\n");
911 calf_line_graph_copy_surface(cache_c, lg->grid_surface);
912 cache_drawn = true;
913 } else if (phase and !realtime_drawn) {
914 // we're in realtime phase and the realtime surface wasn't
915 // reset to cache by now (because there was no cache
916 // phase and no realtime grid was drawn)
917 // so "clear" the realtime surface with the cache
918 if (lg->debug) printf("copy cache->realtime\n");
919 calf_line_graph_copy_surface(realtime_c, lg->cache_surface);
920 realtime_drawn = true;
923 int a;
924 int offset;
925 int move = 0;
926 uint32_t color;
927 for(a = 0;
928 offset = a,
929 color = RGBAtoINT(0.35, 0.4, 0.2, 1),
930 lg->source->get_moving(lg->source_id, a, direction, data, lg->size_x, lg->size_y, offset, color);
931 a++)
933 if (lg->debug) printf("moving %d\n", a);
934 calf_line_graph_draw_moving(lg, ctx, data, direction, offset, color);
935 move += offset;
937 move ++;
938 // set moving distances according to direction
939 int x = 0;
940 int y = 0;
941 switch (direction) {
942 case LG_MOVING_LEFT:
943 default:
944 x = -move;
945 y = 0;
946 break;
947 case LG_MOVING_RIGHT:
948 x = move;
949 y = 0;
950 break;
951 case LG_MOVING_UP:
952 x = 0;
953 y = -move;
954 break;
955 case LG_MOVING_DOWN:
956 x = 0;
957 y = move;
958 break;
960 // copy the old moving surface to the right position on the
961 // new surface
962 if (lg->debug) printf("copy cached moving->moving\n");
963 cairo_set_source_surface(ctx, lg->moving_surface[(int)!lg->movesurf], x, y);
964 cairo_paint(ctx);
966 // switch back to the actual context
967 if (lg->debug) printf("switch to realtime/cache\n");
968 ctx = calf_line_graph_switch_context(lg, _ctx, &cimpl);
970 if (lg->debug) printf("copy moving->realtime/cache\n");
971 calf_line_graph_copy_surface(ctx, lg->moving_surface[lg->movesurf], 1);
973 // toggle the moving cache
974 lg->movesurf = (int)!lg->movesurf;
977 ///////////////////////////////////////////////////////////////
978 // DOTS
979 ///////////////////////////////////////////////////////////////
981 if ((lg->layers & LG_CACHE_DOT and !phase) || (lg->layers & LG_REALTIME_DOT and phase)) {
982 // Cycle through all dots. The plugin is expected to set the x
983 // and y value of the dot.
984 // color of the dot (which can be set by the plugin
985 // via the context) is reset for every graph.
987 if (!cache_drawn and !phase) {
988 // we are drawing dots in cache phase while
989 // the cache wasn't renewed (no graph was drawn), so
990 // prepare the cache surface with the grid surface
991 if (lg->debug) printf("copy grid->cache\n");
992 calf_line_graph_copy_surface(ctx, lg->grid_surface);
993 cache_drawn = true;
995 if (!realtime_drawn and phase) {
996 // we're in realtime phase and the realtime surface wasn't
997 // reset to cache by now (because there was no cache
998 // phase and no realtime grid or graph was drawn)
999 // so "clear" the realtime surface with the cache
1000 if (lg->debug) printf("copy cache->realtime\n");
1001 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
1002 realtime_drawn = true;
1004 for (int a = 0;
1005 cairo_set_source_rgba(ctx, 0.15, 0.2, 0.0, 1),
1006 cairo_set_line_width(ctx, dot_width),
1007 lg->source->get_dot(lg->source_id, a, phase, x, y, size = 3, &cimpl);
1008 a++)
1010 if (lg->debug) printf("dot %d\n", a);
1011 float yv = oy + sy / 2 - (sy / 2 - 1) * y;
1012 cairo_arc(ctx, ox + x * sx, yv, size, 0, 2 * M_PI);
1013 cairo_fill(ctx);
1017 if (!phase) {
1018 // if we have a second cycle for drawing on the realtime
1019 // after the cache was renewed it's time to copy the
1020 // cache to the realtime and switch the target surface
1021 if (lg->debug) printf("switch to realtime\n");
1022 ctx = calf_line_graph_switch_context(lg, realtime_c, &cimpl);
1023 _ctx = realtime_c;
1025 if (cache_drawn) {
1026 // copy the cache to the realtime if it was changed
1027 if (lg->debug) printf("copy cache->realtime\n");
1028 calf_line_graph_copy_surface(ctx, lg->cache_surface, lg->force_cache ? 1 : lg->fade);
1029 realtime_drawn = true;
1030 } else if (grid_drawn) {
1031 // copy the grid to the realtime if it was changed
1032 if (lg->debug) printf("copy grid->realtime\n");
1033 calf_line_graph_copy_surface(ctx, lg->grid_surface, lg->force_cache ? 1 : lg->fade);
1034 realtime_drawn = true;
1037 // check if we can skip the whole realtime phase
1038 if (!(lg->layers & LG_REALTIME_GRID)
1039 and !(lg->layers & LG_REALTIME_GRAPH)
1040 and !(lg->layers & LG_REALTIME_DOT)) {
1041 phase = 2;
1044 } // one or two cycles for drawing cached and non-cached elements
1047 // îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚îš‚
1050 finalize:
1051 delete[] data;
1052 if (lg->debug) printf("\n### finalize\n");
1054 // whatever happened - we need to copy the realtime surface to the
1055 // window surface
1056 //if (lg->debug) printf("switch to window\n");
1057 //ctx = calf_line_graph_switch_context(lg, c, &cimpl);
1058 if (lg->debug) printf("copy realtime->window\n");
1059 calf_line_graph_copy_surface(c, lg->realtime_surface);
1061 // if someone changed the handles via drag'n'drop or externally we
1062 // need a redraw of the handles surface
1063 if (lg->freqhandles and (lg->handle_redraw or lg->force_redraw)) {
1064 cairo_t *hs = cairo_create(lg->handles_surface);
1065 calf_line_graph_clear_surface(hs);
1066 calf_line_graph_draw_freqhandles(lg, hs);
1067 cairo_destroy(hs);
1070 // if we're using frequency handles we need to copy them to the
1071 // window
1072 if (lg->freqhandles) {
1073 if (lg->debug) printf("copy handles->window\n");
1074 calf_line_graph_copy_surface(c, lg->handles_surface);
1077 // and draw the crosshairs on top if neccessary
1078 if (lg->use_crosshairs && lg->crosshairs_active && lg->mouse_x > 0
1079 && lg->mouse_y > 0 && lg->handle_grabbed < 0) {
1080 string s;
1081 s = lg->source->get_crosshair_label((int)(lg->mouse_x - ox), (int)(lg->mouse_y - oy), sx, sy, 1, 1, 1, 1);
1082 cairo_set_line_width(c, 1),
1083 calf_line_graph_draw_crosshairs(lg, c, false, 0, 0.5, 5, false, lg->mouse_x - ox, lg->mouse_y - oy, s, 1.5);
1086 lg->force_cache = false;
1087 lg->force_redraw = false;
1088 lg->handle_redraw = 0;
1089 lg->recreate_surfaces = 0;
1090 lg->layers = 0;
1092 // destroy all temporarily created cairo contexts
1093 cairo_destroy(c);
1094 cairo_destroy(realtime_c);
1095 cairo_destroy(grid_c);
1096 cairo_destroy(cache_c);
1097 cairo_destroy(moving_c[0]);
1098 cairo_destroy(moving_c[1]);
1100 lg->generation += 1;
1102 return TRUE;
1105 static int
1106 calf_line_graph_get_handle_at(CalfLineGraph *lg, double x, double y)
1108 int sx = lg->size_x;
1109 int sy = lg->size_y;
1110 int ox = lg->pad_x;
1111 int oy = lg->pad_y;
1113 sx += sx % 2 - 1;
1114 sy += sy % 2 - 1;
1116 // loop on all handles
1117 for (int i = 0; i < lg->freqhandles; i++) {
1118 FreqHandle *handle = &lg->freq_handles[i];
1119 if (!handle->is_active())
1120 continue;
1122 if (handle->dimensions == 1) {
1123 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1124 if (lg->mouse_x <= ox + round(handle->value_x * sx + HANDLE_WIDTH / 2.0) + 0.5 &&
1125 lg->mouse_x >= ox + round(handle->value_x * sx - HANDLE_WIDTH / 2.0) - 0.5 ) {
1126 return i;
1128 } else if (handle->dimensions >= 2) {
1129 double dx = lg->mouse_x - round(ox + handle->value_x * sx);
1130 double dy = lg->mouse_y - round(oy + handle->value_y * sy);
1132 // if mouse clicked inside circle of HANDLE_WIDTH
1133 if (sqrt(dx * dx + dy * dy) <= HANDLE_WIDTH / 2.0)
1134 return i;
1137 return -1;
1140 static gboolean
1141 calf_line_graph_pointer_motion (GtkWidget *widget, GdkEventMotion *event)
1143 g_assert(CALF_IS_LINE_GRAPH(widget));
1144 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1146 int sx = lg->size_x;
1147 int sy = lg->size_y;
1148 int ox = lg->pad_x;
1149 int oy = lg->pad_y;
1151 sx += sx % 2 - 1;
1152 sy += sy % 2 - 1;
1154 lg->mouse_x = event->x;
1155 lg->mouse_y = event->y;
1157 if (lg->handle_grabbed >= 0) {
1158 FreqHandle *handle = &lg->freq_handles[lg->handle_grabbed];
1160 float new_x_value = float(event->x - ox) / float(sx);
1161 float new_y_value = float(event->y - oy) / float(sy);
1163 if (new_x_value < handle->left_bound) {
1164 new_x_value = handle->left_bound;
1165 } else if (new_x_value > handle->right_bound) {
1166 new_x_value = handle->right_bound;
1169 // restrict y range by top and bottom
1170 if (handle->dimensions >= 2) {
1171 if(new_y_value < 0.0) new_y_value = 0.0;
1172 if(new_y_value > 1.0) new_y_value = 1.0;
1175 if (new_x_value != handle->value_x ||
1176 new_y_value != handle->value_y) {
1177 handle->value_x = new_x_value;
1178 handle->value_y = new_y_value;
1180 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1182 lg->handle_redraw = 1;
1183 calf_line_graph_expose_request(widget, true);
1185 if (event->is_hint)
1187 gdk_event_request_motions(event);
1190 int handle_hovered = calf_line_graph_get_handle_at(lg, event->x, event->y);
1191 if (handle_hovered != lg->handle_hovered) {
1192 if (lg->handle_grabbed >= 0 ||
1193 handle_hovered != -1) {
1194 gdk_window_set_cursor(widget->window, lg->hand_cursor);
1195 lg->handle_hovered = handle_hovered;
1196 } else {
1197 gdk_window_set_cursor(widget->window, lg->arrow_cursor);
1198 lg->handle_hovered = -1;
1200 lg->handle_redraw = 1;
1201 calf_line_graph_expose_request(widget, true);
1203 if(lg->crosshairs_active and lg->use_crosshairs) {
1204 calf_line_graph_expose_request(widget, true);
1206 return TRUE;
1209 static gboolean
1210 calf_line_graph_button_press (GtkWidget *widget, GdkEventButton *event)
1212 g_assert(CALF_IS_LINE_GRAPH(widget));
1213 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1214 bool inside_handle = false;
1216 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1217 if (i != -1)
1219 FreqHandle *handle = &lg->freq_handles[i];
1221 if (handle->dimensions == 1) {
1222 // if user clicked inside a vertical band with width HANDLE_WIDTH handle is considered grabbed
1223 lg->handle_grabbed = i;
1224 inside_handle = true;
1226 if (lg->enforce_handle_order) {
1227 // look for previous one dimensional handle to find left_bound
1228 for (int j = i - 1; j >= 0; j--) {
1229 FreqHandle *prevhandle = &lg->freq_handles[j];
1230 if(prevhandle->is_active() && prevhandle->dimensions == 1) {
1231 handle->left_bound = prevhandle->value_x + lg->min_handle_distance;
1232 break;
1236 // look for next one dimensional handle to find right_bound
1237 for (int j = i + 1; j < lg->freqhandles; j++) {
1238 FreqHandle *nexthandle = &lg->freq_handles[j];
1239 if(nexthandle->is_active() && nexthandle->dimensions == 1) {
1240 handle->right_bound = nexthandle->value_x - lg->min_handle_distance;
1241 break;
1245 } else if (handle->dimensions >= 2) {
1246 lg->handle_grabbed = i;
1247 inside_handle = true;
1251 if (inside_handle && event->type == GDK_2BUTTON_PRESS) {
1252 FreqHandle &handle = lg->freq_handles[lg->handle_grabbed];
1253 handle.value_x = handle.default_value_x;
1254 handle.value_y = handle.default_value_y;
1255 g_signal_emit_by_name(widget, "freqhandle-changed", &handle);
1258 if(!inside_handle) {
1259 lg->crosshairs_active = !lg->crosshairs_active;
1262 calf_line_graph_expose_request(widget, true);
1263 gtk_widget_grab_focus(widget);
1264 gtk_grab_add(widget);
1266 return TRUE;
1269 static gboolean
1270 calf_line_graph_button_release (GtkWidget *widget, GdkEventButton *event)
1272 g_assert(CALF_IS_LINE_GRAPH(widget));
1273 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1275 lg->handle_grabbed = -1;
1277 if (GTK_WIDGET_HAS_GRAB(widget))
1278 gtk_grab_remove(widget);
1280 calf_line_graph_expose_request(widget, true);
1281 return TRUE;
1284 static gboolean
1285 calf_line_graph_scroll (GtkWidget *widget, GdkEventScroll *event)
1287 g_assert(CALF_IS_LINE_GRAPH(widget));
1288 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1290 int i = calf_line_graph_get_handle_at(lg, lg->mouse_x, lg->mouse_y);
1291 if (i != -1)
1293 FreqHandle *handle = &lg->freq_handles[i];
1294 if (handle->dimensions == 3) {
1295 if (event->direction == GDK_SCROLL_UP) {
1296 handle->value_z += 0.05;
1297 if(handle->value_z > 1.0) {
1298 handle->value_z = 1.0;
1300 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1301 } else if (event->direction == GDK_SCROLL_DOWN) {
1302 handle->value_z -= 0.05;
1303 if(handle->value_z < 0.0) {
1304 handle->value_z = 0.0;
1306 g_signal_emit_by_name(widget, "freqhandle-changed", handle);
1308 lg->handle_redraw = 1;
1311 return TRUE;
1314 static gboolean
1315 calf_line_graph_enter (GtkWidget *widget, GdkEventCrossing *event)
1317 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1319 if (lg->debug) printf("[enter]\n");
1320 return TRUE;
1323 static gboolean
1324 calf_line_graph_leave (GtkWidget *widget, GdkEventCrossing *event)
1327 g_assert(CALF_IS_LINE_GRAPH(widget));
1328 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1330 if (lg->debug) printf("[leave]\n");
1331 if (lg->mouse_x >= 0 or lg->mouse_y >= 0)
1332 calf_line_graph_expose_request(widget, true);
1333 lg->mouse_x = -1;
1334 lg->mouse_y = -1;
1336 return TRUE;
1340 void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square)
1342 g_assert(CALF_IS_LINE_GRAPH(graph));
1343 graph->is_square = is_square;
1346 static void
1347 calf_line_graph_size_request (GtkWidget *widget,
1348 GtkRequisition *requisition)
1350 g_assert(CALF_IS_LINE_GRAPH(widget));
1352 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1354 if (lg->debug) printf("size request\n");
1357 static void
1358 calf_line_graph_size_allocate (GtkWidget *widget,
1359 GtkAllocation *allocation)
1361 g_assert(CALF_IS_LINE_GRAPH(widget));
1362 CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
1364 if (lg->debug) printf("size allocation\n");
1366 GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_LINE_GRAPH_GET_CLASS( lg ) );
1368 // remember the allocation
1369 widget->allocation = *allocation;
1371 // reset the allocation if a square widget is requested
1372 GtkAllocation &a = widget->allocation;
1373 if (lg->is_square)
1375 if (a.width > a.height)
1377 a.x += (a.width - a.height) / 2;
1378 a.width = a.height;
1380 if (a.width < a.height)
1382 a.y += (a.height - a.width) / 2;
1383 a.height = a.width;
1387 lg->size_x = a.width - lg->pad_x * 2;
1388 lg->size_y = a.height - lg->pad_y * 2;
1390 lg->recreate_surfaces = 1;
1391 parent_class->size_allocate( widget, &a );
1395 static void
1396 calf_line_graph_class_init (CalfLineGraphClass *klass)
1398 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1399 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
1400 widget_class->expose_event = calf_line_graph_expose;
1401 widget_class->size_request = calf_line_graph_size_request;
1402 widget_class->size_allocate = calf_line_graph_size_allocate;
1403 widget_class->button_press_event = calf_line_graph_button_press;
1404 widget_class->button_release_event = calf_line_graph_button_release;
1405 widget_class->motion_notify_event = calf_line_graph_pointer_motion;
1406 widget_class->scroll_event = calf_line_graph_scroll;
1407 widget_class->enter_notify_event = calf_line_graph_enter;
1408 widget_class->leave_notify_event = calf_line_graph_leave;
1410 g_signal_new("freqhandle-changed",
1411 G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST,
1412 0, NULL, NULL,
1413 g_cclosure_marshal_VOID__POINTER,
1414 G_TYPE_NONE, 1, G_TYPE_POINTER);
1417 static void
1418 calf_line_graph_unrealize (GtkWidget *widget, CalfLineGraph *lg)
1420 if (lg->debug) printf("unrealize\n");
1421 calf_line_graph_destroy_surfaces(widget);
1424 static void
1425 calf_line_graph_init (CalfLineGraph *lg)
1427 GtkWidget *widget = GTK_WIDGET(lg);
1429 if (lg->debug) printf("lg init\n");
1431 GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS | GTK_SENSITIVE | GTK_PARENT_SENSITIVE);
1432 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);
1434 widget->requisition.width = 40;
1435 widget->requisition.height = 40;
1436 lg->force_cache = true;
1437 lg->force_redraw = false;
1438 lg->zoom = 1;
1439 lg->param_zoom = -1;
1440 lg->offset = 0;
1441 lg->param_offset = -1;
1442 lg->recreate_surfaces = 1;
1443 lg->mode = 0;
1444 lg->movesurf = 0;
1445 lg->generation = 0;
1446 lg->arrow_cursor = gdk_cursor_new(GDK_LEFT_PTR);
1447 lg->hand_cursor = gdk_cursor_new(GDK_FLEUR);
1448 lg->layers = LG_CACHE_GRID | LG_CACHE_GRAPH
1449 | LG_CACHE_DOT | LG_CACHE_MOVING
1450 | LG_REALTIME_GRID | LG_REALTIME_GRAPH
1451 | LG_REALTIME_DOT | LG_REALTIME_MOVING;
1453 g_signal_connect(GTK_OBJECT(widget), "unrealize", G_CALLBACK(calf_line_graph_unrealize), (gpointer)lg);
1455 for(int i = 0; i < FREQ_HANDLES; i++) {
1456 FreqHandle *handle = &lg->freq_handles[i];
1457 handle->active = false;
1458 handle->param_active_no = -1;
1459 handle->param_x_no = -1;
1460 handle->param_y_no = -1;
1461 handle->value_x = -1.0;
1462 handle->value_y = -1.0;
1463 handle->param_x_no = -1;
1464 handle->label = NULL;
1465 handle->left_bound = 0.0 + lg->min_handle_distance;
1466 handle->right_bound = 1.0 - lg->min_handle_distance;
1469 lg->handle_grabbed = -1;
1470 lg->handle_hovered = -1;
1471 lg->handle_redraw = 1;
1472 lg->min_handle_distance = 0.025;
1474 lg->background_surface = NULL;
1475 lg->grid_surface = NULL;
1476 lg->cache_surface = NULL;
1477 lg->moving_surface[0] = NULL;
1478 lg->moving_surface[1] = NULL;
1479 lg->handles_surface = NULL;
1480 lg->realtime_surface = NULL;
1483 GtkWidget *
1484 calf_line_graph_new()
1486 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH, NULL ));
1489 GType
1490 calf_line_graph_get_type (void)
1492 static GType type = 0;
1493 if (!type) {
1494 static const GTypeInfo type_info = {
1495 sizeof(CalfLineGraphClass),
1496 NULL, /* base_init */
1497 NULL, /* base_finalize */
1498 (GClassInitFunc)calf_line_graph_class_init,
1499 NULL, /* class_finalize */
1500 NULL, /* class_data */
1501 sizeof(CalfLineGraph),
1502 0, /* n_preallocs */
1503 (GInstanceInitFunc)calf_line_graph_init
1506 GTypeInfo *type_info_copy = new GTypeInfo(type_info);
1508 for (int i = 0; ; i++) {
1509 char *name = g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init) >> 16, i);
1510 if (g_type_from_name(name)) {
1511 free(name);
1512 continue;
1514 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
1515 name,
1516 type_info_copy,
1517 (GTypeFlags)0);
1518 free(name);
1519 break;
1522 return type;