Stop using gdk_scroll_window() seems to just invalidate the complete
[terminatorX.git] / src / tX_widget.c
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2016  Alexander K├Ânig
4  
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9  
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14  
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  
18     File: tX_widget.c
19  
20     Description: This contains the implementation of the tx_widget.
21                  This file is based on the GTK+ widget example from
22                  the GTK+ 1.2 tutorial.
23 */
24
25 #include <math.h>
26
27 #include <gtk/gtk.h>
28 #include "tX_widget.h"
29 #include "tX_types.h"
30 #include "tX_global.h"
31 #include <malloc.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #ifndef WIN32
36 #include <unistd.h>
37 #endif
38
39 #define TX_DEFAULT_SIZE_X 100
40 #define TX_DEFAULT_SIZE_Y 11
41
42 /* forward declaration */
43 static void gtk_tx_class_init(GtkTxClass *);
44 static void gtk_tx_init(GtkTx * tx);
45 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples);
46 static void gtk_tx_destroy(GtkWidget * widget);
47 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples);
48 static void gtk_tx_realize(GtkWidget * widget);
49
50 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_height, gint *natural_height);
51 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height);
52
53 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation);
54 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr);
55 static void gtk_tx_prepare(GtkWidget * widget);
56
57 /* data */
58 static GtkWidgetClass *parent_class = NULL;
59
60 /* widget "methods" */
61
62 GType gtk_tx_get_type() {
63         static GType tx_type = 0;
64
65         if (!tx_type) {
66                 static const GTypeInfo tx_info = {
67                         sizeof (GtkTxClass),
68                         NULL,
69                         NULL,
70                         (GClassInitFunc) gtk_tx_class_init, 
71                         NULL,
72                         NULL,
73                         sizeof (GtkTx),
74                         0,
75                         (GInstanceInitFunc) gtk_tx_init,
76                 };
77
78                 tx_type = g_type_register_static(GTK_TYPE_WIDGET, "GtkTx", &tx_info, 0);
79         }
80         
81         return tx_type;
82 }
83
84 static void gtk_tx_class_init(GtkTxClass * gclass) {
85         GtkWidgetClass *widget_class;
86         
87         widget_class = (GtkWidgetClass *) gclass;
88         parent_class = (GtkWidgetClass *) g_type_class_peek(gtk_widget_get_type());
89
90         widget_class->destroy = gtk_tx_destroy; 
91         widget_class->realize = gtk_tx_realize;
92         widget_class->draw = gtk_tx_draw;
93         widget_class->get_preferred_height = gtk_tx_get_preferred_height;
94         widget_class->get_preferred_width = gtk_tx_get_preferred_width;
95         widget_class->size_allocate = gtk_tx_size_allocate;
96 }
97
98 #define COL_BG_FOCUS     0
99 #define COL_BG_NO_FOCUS  1
100 #define COL_FG_FOCUS     2
101 #define COL_FG_NO_FOCUS  3
102 #define COL_CURSOR       4
103 #define COL_CURSOR_MUTE  5
104
105 void gtk_tx_update_colors(GtkTx *tx) {
106         gdk_rgba_parse(&tx->colors[COL_BG_FOCUS], globals.wav_display_bg_focus);
107         tx->colors[COL_BG_FOCUS].alpha=1.0;
108         gdk_rgba_parse(&tx->colors[COL_BG_NO_FOCUS], globals.wav_display_bg_no_focus);
109         tx->colors[COL_BG_NO_FOCUS].alpha=1.0;
110         
111         gdk_rgba_parse(&tx->colors[COL_FG_FOCUS], globals.wav_display_fg_focus);
112         tx->colors[COL_FG_FOCUS].alpha=1.0;
113         gdk_rgba_parse(&tx->colors[COL_FG_NO_FOCUS], globals.wav_display_fg_no_focus);
114         tx->colors[COL_FG_NO_FOCUS].alpha=1.0;
115         
116         gdk_rgba_parse(&tx->colors[COL_CURSOR], globals.wav_display_cursor);
117         tx->colors[COL_CURSOR].alpha=1.0;
118         gdk_rgba_parse(&tx->colors[COL_CURSOR_MUTE], globals.wav_display_cursor_mute);
119         tx->colors[COL_CURSOR_MUTE].alpha=1.0;
120 }
121
122
123 static void gtk_tx_init(GtkTx * tx) {
124         tx->disp_data = NULL;
125         tx->data = NULL;
126         tx->samples = 0;
127 #ifdef USE_DISPLAY_NORMALIZE
128         tx->max_value=-1;
129 #endif
130         
131         memset(tx->colors, 0, sizeof(tx->colors));
132         
133         gtk_tx_update_colors(tx);
134         
135         tx->current_fg=tx->audio_colors_focus;
136         tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
137         
138         tx->audio_colors_focus = NULL;
139         tx->audio_colors_nofocus = NULL;
140         
141         tx->spp=1;
142         tx->zoom=0;
143         tx->cursor_pos=0;
144         tx->cursor_x_pos=0;
145         
146         tx->surface = NULL;
147 }
148
149 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples) {
150         GtkTx *tx;
151
152         tx = (GtkTx *) g_object_new(gtk_tx_get_type (), NULL);
153
154         tx->data = wavdata;
155         tx->samples = wavsamples;
156         tx->zoom=0;
157         tx->display_x_offset=0;
158         
159         return GTK_WIDGET(tx);
160 }
161
162 static void gtk_tx_destroy(GtkWidget * widget) {
163         GtkTx *tx;
164         g_return_if_fail(widget != NULL);
165         g_return_if_fail(GTK_IS_TX(widget));
166
167         tx=GTK_TX(widget);
168         
169         if (tx->disp_data) { 
170                 free(tx->disp_data);
171                 tx->disp_data=NULL;
172         }
173         
174         if (GTK_WIDGET_CLASS(parent_class)->destroy) {
175                 (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
176         }       
177 }
178
179 #define MAX_ZOOM_WIDTH 500000.0
180
181 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples) {
182         g_return_if_fail(tx != NULL);
183         g_return_if_fail(GTK_IS_TX(tx));
184
185         tx->data = wavdata;
186         tx->samples = wavsamples;
187 #ifdef USE_DISPLAY_NORMALIZE    
188         tx->max_value=-1;
189 #endif
190         
191         gtk_tx_prepare(GTK_WIDGET(tx));
192         gtk_widget_queue_draw(GTK_WIDGET(tx));
193 }
194
195 static void gtk_tx_realize(GtkWidget * widget) {
196         GdkWindowAttr attributes;
197         gint attributes_mask;
198         GtkTx *tx;
199         
200         g_return_if_fail(widget != NULL);
201         g_return_if_fail(GTK_IS_TX(widget));
202
203         tx = GTK_TX(widget);
204         gtk_widget_set_realized(widget, TRUE);
205
206         GtkAllocation allocation;
207         gtk_widget_get_allocation(widget, &allocation);
208         attributes.x = allocation.x;
209         attributes.y = allocation.y;
210         attributes.width = allocation.width;
211         attributes.height = allocation.height;
212         attributes.wclass = GDK_INPUT_OUTPUT;
213         attributes.window_type = GDK_WINDOW_CHILD;
214         attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
215         attributes.visual = gtk_widget_get_visual(widget);
216         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
217
218         gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
219
220         gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
221
222         if (tx->surface) {
223                 cairo_surface_destroy(tx->surface);
224         }
225         
226         tx->surface = gdk_window_create_similar_surface(gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation.width, allocation.height);
227 }
228
229 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) {
230   *minimal_width = *natural_width = TX_DEFAULT_SIZE_X;
231 }
232
233 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) {
234     *minimal_height = *natural_height = TX_DEFAULT_SIZE_Y;
235 }
236
237 static void gtk_tx_reallocate_disp_data(GtkWidget *widget) {
238         GtkAllocation allocation;
239         GtkTx *tx = GTK_TX(widget);
240         int x, sample, avg_pos;
241         int16_t *ptr;
242         f_prec value;
243
244         gtk_widget_get_allocation(widget, &allocation);
245
246         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
247
248         if (tx->data) {
249                 int max_spp=tx->samples/allocation.width;
250                 int min_spp=tx->samples/MAX_ZOOM_WIDTH;
251                 gdouble diff;
252                 
253                 if (min_spp==0) min_spp=1;
254                 
255                 diff=max_spp-min_spp;
256                 
257                 tx->spp=min_spp+diff*(1.0-tx->zoom);
258                 tx->display_width = tx->samples/tx->spp;
259                 
260 #ifdef USE_DISPLAY_NORMALIZE    
261                 tx->max_value=-1;
262 #endif          
263                 
264             tx->disp_data = (int16_t *) malloc(tx->display_width * sizeof(int16_t));
265
266             if (tx->disp_data) {                        
267 #ifdef USE_DISPLAY_NORMALIZE            
268                         if (tx->max_value==-1) {
269                                 /* We haven't figured a max value yet... */
270                                 //puts("searching max...");
271                 
272                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
273                                         value = 0;
274                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
275                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
276                                         }
277                                         if (value>tx->max_value) tx->max_value=value;
278                                         tx->disp_data[x] = value; 
279                                 }
280                                 for (x = 0; x < tx->display_width; x++) {
281                                         f_prec t=tx->disp_data[x]/(double) tx->max_value;
282                                         tx->disp_data[x]=(int) (t * (f_prec) (tx->yc));
283                                 }
284                         } else {
285 #endif                          
286                                 //puts("have max...");
287                                 /* We have a max value... */
288                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
289                                         f_prec t;
290                                         value = 0;
291                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
292                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
293                                         }
294 #ifdef USE_DISPLAY_NORMALIZE                                    
295                                         t=value/(double) tx->max_value;
296 #else
297                                         t=value/32768.0;
298 #endif                                  
299                                         tx->disp_data[x] = (int) (t * (f_prec) (tx->yc)); 
300                                 }
301 #ifdef USE_DISPLAY_NORMALIZE
302                         }
303 #endif                  
304                 }
305         } else {
306             tx->disp_data = NULL;
307         }
308         
309 }
310
311 static void gtk_tx_prepare(GtkWidget * widget) {
312         GtkTx *tx;
313         int color;
314
315         g_return_if_fail(widget != NULL);
316         g_return_if_fail(GTK_IS_TX(widget));
317
318         tx = GTK_TX(widget);
319         
320         GtkAllocation allocation;
321         GdkRGBA midColor;
322         gboolean fg = (tx->current_fg == tx->audio_colors_focus);
323         
324         if (tx->audio_colors_focus) { 
325                 free(tx->audio_colors_focus); 
326                 free(tx->audio_colors_nofocus); 
327                 
328                 tx->audio_colors_focus = NULL; 
329                 tx->audio_colors_nofocus = NULL; 
330         } else {
331                 fg = FALSE;
332         }
333         
334         // update tx->yc
335         
336         gtk_widget_get_allocation(widget, &allocation);
337         tx->xc = allocation.width / 2;
338         tx->xmax = allocation.width;
339         tx->yc = allocation.height / 2;
340         tx->ymax = allocation.height;
341
342         // allocate colors
343         
344         tx->audio_colors_focus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
345         tx->audio_colors_nofocus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
346         
347         // no focus colors
348
349         midColor.red = tx->colors[COL_BG_NO_FOCUS].red + (tx->colors[COL_FG_NO_FOCUS].red - tx->colors[COL_BG_NO_FOCUS].red) / 4;
350         midColor.green = tx->colors[COL_BG_NO_FOCUS].green + (tx->colors[COL_FG_NO_FOCUS].green - tx->colors[COL_BG_NO_FOCUS].green) / 4;
351         midColor.blue = tx->colors[COL_BG_NO_FOCUS].blue + (tx->colors[COL_FG_NO_FOCUS].blue - tx->colors[COL_BG_NO_FOCUS].blue) / 4;
352         
353         for (color=0 ; color < tx->yc; color++) {
354                 float dist = (float) color / (float) tx->yc;
355                 
356                 tx->audio_colors_nofocus[color].red = midColor.red + dist*(tx->colors[COL_FG_NO_FOCUS].red - midColor.red);
357                 tx->audio_colors_nofocus[color].green = midColor.green + dist*(tx->colors[COL_FG_NO_FOCUS].green - midColor.green);
358                 tx->audio_colors_nofocus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_NO_FOCUS].blue - midColor.blue);
359                 tx->audio_colors_nofocus[color].alpha = 1.0;
360         }
361         // focus colors
362
363         midColor.red = tx->colors[COL_BG_FOCUS].red + (tx->colors[COL_FG_FOCUS].red - tx->colors[COL_BG_FOCUS].red) / 4;
364         midColor.green = tx->colors[COL_BG_FOCUS].green + (tx->colors[COL_FG_FOCUS].green - tx->colors[COL_BG_FOCUS].green) / 4;
365         midColor.blue = tx->colors[COL_BG_FOCUS].blue + (tx->colors[COL_FG_FOCUS].blue - tx->colors[COL_BG_FOCUS].blue) / 4;
366         
367         for (color=0 ; color < tx->yc; color++) {
368                 float dist = (float) color / (float) tx->yc;
369                 
370                 tx->audio_colors_focus[color].red = midColor.red + dist*(tx->colors[COL_FG_FOCUS].red - midColor.red);
371                 tx->audio_colors_focus[color].green = midColor.green + dist*(tx->colors[COL_FG_FOCUS].green - midColor.green);
372                 tx->audio_colors_focus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_FOCUS].blue - midColor.blue);
373                 tx->audio_colors_focus[color].alpha = 1.0;
374         }
375         
376         if (fg) {
377                 tx->current_fg = tx->audio_colors_focus;
378         } else {
379                 tx->current_fg = tx->audio_colors_nofocus;
380         }
381         
382         gtk_tx_reallocate_disp_data(widget);
383 }
384
385 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation) {
386         g_return_if_fail(widget != NULL);
387         g_return_if_fail(GTK_IS_TX(widget));
388         g_return_if_fail(allocation != NULL);
389
390         gtk_widget_set_allocation(widget, allocation);
391
392         gtk_tx_prepare(widget);
393
394         if (gtk_widget_get_realized(widget)) {
395 #ifdef USE_DISPLAY_NORMALIZE            
396                 GtkTx *tx = GTK_TX(widget);
397                 tx->max_value=-1;
398 #endif          
399             gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height);
400         }
401 }
402
403 void gtk_tx_set_zoom(GtkTx *tx, f_prec zoom, int is_playing) {
404         GtkWidget *wid=GTK_WIDGET(tx);
405         
406         if (zoom != tx->zoom) {
407                 tx->zoom=zoom;
408                 gtk_tx_reallocate_disp_data(wid);
409                 if (!is_playing || (zoom < 0.01)) {
410                         gtk_widget_queue_draw(wid);
411                 }
412         }
413 }
414
415 #define draw_line(x1, y1, x2, y2, rgba) { gdk_cairo_set_source_rgba(cr, rgba); cairo_move_to(cr, x1, y1); cairo_line_to(cr, x2, y2); cairo_stroke(cr); }
416 #define draw_sample(x, y1, y2, rgba) draw_line(x, y1, x, y2, rgba)
417 #define draw_rectangle(rect, rgba) { gdk_cairo_set_source_rgba(cr, rgba); cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height); cairo_fill(cr); }
418
419 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr) {
420         GtkTx *tx;
421         gint x;
422         GdkRectangle area;
423         
424         g_return_val_if_fail(widget != NULL, FALSE);
425         g_return_val_if_fail(GTK_IS_TX(widget), FALSE);
426
427         tx = GTK_TX(widget);
428         
429         gdk_cairo_get_clip_rectangle(cr, &area);
430         
431         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
432         cairo_set_source_surface(cr, tx->surface, 0, 0);
433         cairo_set_line_width(cr, 1);
434
435         if (tx->disp_data) {
436                 int src_x;
437
438                 int x_offset;
439                 if (tx->zoom > 0.0) {
440                     x_offset= tx->cursor_pos > tx->xc ? tx->cursor_pos-tx->xc : 0;
441                         if (x_offset+tx->xmax > tx->display_width) {
442                                 x_offset = tx->display_width-tx->xmax;
443                         }
444                 } else {
445                     x_offset = 0;                       
446                 }
447
448                 tx->cursor_x_pos = tx->cursor_pos-x_offset;
449                 tx->display_x_offset = x_offset;
450
451                 draw_rectangle(area, tx->current_bg);
452                 for (x=area.x, src_x=tx->display_x_offset+area.x; x < area.x + area.width; x++, src_x++) {
453                         if (x!=tx->cursor_x_pos) {
454                                 int dy = tx->disp_data[src_x];
455                                 draw_sample(x, tx->yc-dy, tx->yc+dy+1, &tx->current_fg[dy]);
456                         }
457                 }
458
459                 /* draw cursor */
460                 draw_sample(tx->cursor_x_pos, 0, tx->ymax, tx->mute ? &tx->colors[COL_CURSOR_MUTE] : &tx->colors[COL_CURSOR]);
461         } else {
462                 draw_rectangle(area, tx->current_bg);
463                 draw_line(area.x, tx->yc, area.width, tx->yc, tx->current_fg);
464         }
465         
466         return FALSE;
467 }
468
469 void gtk_tx_update_pos_display(GtkTx * tx, int sample, int mute) {
470         GtkWidget *widget = GTK_WIDGET(tx);
471         
472         tx->cursor_pos = sample / tx->spp;
473         tx->mute = mute;
474
475         gtk_widget_queue_draw(widget);
476 }
477
478 void gtk_tx_cleanup_pos_display(GtkTx * tx) {
479         GtkWidget *widget;
480         GtkAllocation allocation;
481
482         widget = GTK_WIDGET(tx);
483         gtk_widget_get_allocation(widget, &allocation);
484
485         tx->display_x_offset=0;
486         tx->cursor_pos=-1;
487         tx->cursor_x_pos=-1;
488         
489         gtk_widget_queue_draw(widget);
490 }
491
492 void gtk_tx_show_focus(GtkTx *tx, int show) {
493         if (show) {
494                 tx->current_fg=tx->audio_colors_focus;
495                 tx->current_bg=&tx->colors[COL_BG_FOCUS];
496         } else {
497                 tx->current_fg=tx->audio_colors_nofocus;
498                 tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
499         }
500         
501         gtk_widget_queue_draw(GTK_WIDGET(tx));  
502 }
503
504 f_prec gtk_tx_get_zoom(GtkTx *tx) {
505         return tx->zoom;
506 }