Implement render buffers for tX_widget to work around lost efficiency
[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 static void gtk_tx_update_render_buffer(GtkTx* tx);
57
58 /* data */
59 static GtkWidgetClass *parent_class = NULL;
60
61 /* widget "methods" */
62
63 GType gtk_tx_get_type() {
64         static GType tx_type = 0;
65
66         if (!tx_type) {
67                 static const GTypeInfo tx_info = {
68                         sizeof (GtkTxClass),
69                         NULL,
70                         NULL,
71                         (GClassInitFunc) gtk_tx_class_init, 
72                         NULL,
73                         NULL,
74                         sizeof (GtkTx),
75                         0,
76                         (GInstanceInitFunc) gtk_tx_init,
77                 };
78
79                 tx_type = g_type_register_static(GTK_TYPE_WIDGET, "GtkTx", &tx_info, 0);
80         }
81         
82         return tx_type;
83 }
84
85 static void gtk_tx_class_init(GtkTxClass * gclass) {
86         GtkWidgetClass *widget_class;
87         
88         widget_class = (GtkWidgetClass *) gclass;
89         parent_class = (GtkWidgetClass *) g_type_class_peek(gtk_widget_get_type());
90
91         widget_class->destroy = gtk_tx_destroy; 
92         widget_class->realize = gtk_tx_realize;
93         widget_class->draw = gtk_tx_draw;
94         widget_class->get_preferred_height = gtk_tx_get_preferred_height;
95         widget_class->get_preferred_width = gtk_tx_get_preferred_width;
96         widget_class->size_allocate = gtk_tx_size_allocate;
97 }
98
99 #define COL_FOCUS 0 
100 #define COL_NO_FOCUS 1
101
102 #define COL_BG_FOCUS     0
103 #define COL_BG_NO_FOCUS  1
104 #define COL_FG_FOCUS     2
105 #define COL_FG_NO_FOCUS  3
106 #define COL_CURSOR       4
107 #define COL_CURSOR_MUTE  5
108
109 void gtk_tx_update_colors(GtkTx *tx) {
110         int step;
111
112         gdk_rgba_parse(&tx->colors[COL_BG_FOCUS], globals.wav_display_bg_focus);
113         tx->colors[COL_BG_FOCUS].alpha=1.0;
114         gdk_rgba_parse(&tx->colors[COL_BG_NO_FOCUS], globals.wav_display_bg_no_focus);
115         tx->colors[COL_BG_NO_FOCUS].alpha=1.0;
116         
117         gdk_rgba_parse(&tx->colors[COL_FG_FOCUS], globals.wav_display_fg_focus);
118         tx->colors[COL_FG_FOCUS].alpha=1.0;
119         gdk_rgba_parse(&tx->colors[COL_FG_NO_FOCUS], globals.wav_display_fg_no_focus);
120         tx->colors[COL_FG_NO_FOCUS].alpha=1.0;
121         
122         gdk_rgba_parse(&tx->colors[COL_CURSOR], globals.wav_display_cursor);
123         tx->colors[COL_CURSOR].alpha=1.0;
124         gdk_rgba_parse(&tx->colors[COL_CURSOR_MUTE], globals.wav_display_cursor_mute);
125         tx->colors[COL_CURSOR_MUTE].alpha=1.0;
126
127         for (step = 0; step < GTK_TX_HISTORY_LENGTH; step++) {
128                 double frac = (1.0 / ((double) GTK_TX_HISTORY_LENGTH + 2.0)) * ((double) step + 1.0);
129
130                 GdkRGBA *color = &tx->history_colors[step];
131                 color->red = tx->colors[COL_CURSOR].red;
132                 color->green = tx->colors[COL_CURSOR].green;
133                 color->blue = tx->colors[COL_CURSOR].blue;
134                 color->alpha = frac*frac/2;
135         }
136 }
137
138
139 static void gtk_tx_init(GtkTx * tx) {
140         tx->disp_data = NULL;
141         tx->data = NULL;
142         tx->samples = 0;
143 #ifdef USE_DISPLAY_NORMALIZE
144         tx->max_value=-1;
145 #endif
146         
147         memset(tx->colors, 0, sizeof(tx->colors));
148         memset(tx->history_colors, 0, sizeof(tx->history_colors));
149
150         gtk_tx_update_colors(tx);
151         
152         tx->current_fg=tx->audio_colors_focus;
153         tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
154         
155         tx->audio_colors_focus = NULL;
156         tx->audio_colors_nofocus = NULL;
157         
158         tx->spp=1;
159         tx->zoom=0;
160         tx->cursor_pos=0;
161         tx->cursor_x_pos=0;
162         
163         tx->render_buffer_surface_a = NULL;
164         tx->render_buffer_surface_b = NULL;
165         tx->current_render_buffer_surface = NULL;
166         tx->previous_render_buffer_surface = NULL;
167
168         tx->render_buffer_x_offset = -1;
169         tx->render_buffer_display_width = -1;
170         tx->render_buffer_fg = NULL;
171 }
172
173 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples) {
174         GtkTx *tx;
175
176         tx = (GtkTx *) g_object_new(gtk_tx_get_type (), NULL);
177
178         tx->data = wavdata;
179         tx->samples = wavsamples;
180         tx->zoom=0;
181         tx->display_x_offset=0;
182         
183         return GTK_WIDGET(tx);
184 }
185
186 static void gtk_tx_destroy(GtkWidget * widget) {
187         GtkTx *tx;
188         g_return_if_fail(widget != NULL);
189         g_return_if_fail(GTK_IS_TX(widget));
190
191         tx=GTK_TX(widget);
192         
193         if (tx->render_buffer_surface_a) {
194                 cairo_surface_destroy(tx->render_buffer_surface_a);
195                 cairo_surface_destroy(tx->render_buffer_surface_b);
196                 tx->render_buffer_surface_a = NULL; 
197                 tx->render_buffer_surface_b = NULL;
198         }
199
200         if (tx->disp_data) { 
201                 free(tx->disp_data);
202                 tx->disp_data=NULL;
203         }
204         
205         if (GTK_WIDGET_CLASS(parent_class)->destroy) {
206                 (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
207         }       
208 }
209
210 #define MAX_ZOOM_WIDTH 500000.0
211
212 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples) {
213         g_return_if_fail(tx != NULL);
214         g_return_if_fail(GTK_IS_TX(tx));
215
216         tx->data = wavdata;
217         tx->samples = wavsamples;
218 #ifdef USE_DISPLAY_NORMALIZE    
219         tx->max_value=-1;
220 #endif
221         
222         gtk_tx_prepare(GTK_WIDGET(tx));
223         gtk_widget_queue_draw(GTK_WIDGET(tx));
224 }
225
226 static void gtk_tx_realize(GtkWidget * widget) {
227         GdkWindowAttr attributes;
228         gint attributes_mask;
229         
230         g_return_if_fail(widget != NULL);
231         g_return_if_fail(GTK_IS_TX(widget));
232
233         gtk_widget_set_realized(widget, TRUE);
234
235         GtkAllocation allocation;
236         gtk_widget_get_allocation(widget, &allocation);
237         attributes.x = allocation.x;
238         attributes.y = allocation.y;
239         attributes.width = allocation.width;
240         attributes.height = allocation.height;
241         attributes.wclass = GDK_INPUT_OUTPUT;
242         attributes.window_type = GDK_WINDOW_CHILD;
243         attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
244         attributes.visual = gtk_widget_get_visual(widget);
245         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
246
247         gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
248
249         gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
250 }
251
252 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) {
253   *minimal_width = *natural_width = TX_DEFAULT_SIZE_X;
254 }
255
256 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) {
257         *minimal_height = *natural_height = TX_DEFAULT_SIZE_Y;
258 }
259
260 static void gtk_tx_reallocate_disp_data(GtkWidget *widget) {
261         GtkAllocation allocation;
262         GtkTx *tx = GTK_TX(widget);
263         int x, sample, avg_pos;
264         int16_t *ptr;
265         f_prec value;
266
267         gtk_widget_get_allocation(widget, &allocation);
268
269         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
270
271         if (tx->data) {
272                 int max_spp=tx->samples/allocation.width;
273                 int min_spp=tx->samples/MAX_ZOOM_WIDTH;
274                 gdouble diff;
275                 
276                 if (min_spp==0) min_spp=1;
277                 
278                 diff=max_spp-min_spp;
279                 
280                 tx->spp=min_spp+diff*(1.0-tx->zoom);
281                 tx->display_width = tx->samples/tx->spp;
282                 
283 #ifdef USE_DISPLAY_NORMALIZE    
284                 tx->max_value=-1;
285 #endif          
286                 
287             tx->disp_data = (int16_t *) malloc(tx->display_width * sizeof(int16_t));
288
289             if (tx->disp_data) {                        
290 #ifdef USE_DISPLAY_NORMALIZE            
291                         if (tx->max_value==-1) {
292                                 /* We haven't figured a max value yet... */
293                                 //puts("searching max...");
294                 
295                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
296                                         value = 0;
297                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
298                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
299                                         }
300                                         if (value>tx->max_value) tx->max_value=value;
301                                         tx->disp_data[x] = value; 
302                                 }
303                                 for (x = 0; x < tx->display_width; x++) {
304                                         f_prec t=tx->disp_data[x]/(double) tx->max_value;
305                                         tx->disp_data[x]=(int) (t * (f_prec) (tx->yc));
306                                 }
307                         } else {
308 #endif                          
309                                 //puts("have max...");
310                                 /* We have a max value... */
311                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
312                                         f_prec t;
313                                         value = 0;
314                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
315                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
316                                         }
317 #ifdef USE_DISPLAY_NORMALIZE                                    
318                                         t=value/(double) tx->max_value;
319 #else
320                                         t=value/32768.0;
321 #endif                                  
322                                         tx->disp_data[x] = (int) (t * (f_prec) (tx->yc)); 
323                                 }
324 #ifdef USE_DISPLAY_NORMALIZE
325                         }
326 #endif                  
327                 }
328         } else {
329             tx->disp_data = NULL;
330         }
331         
332 }
333
334 static void gtk_tx_prepare(GtkWidget * widget) {
335         GtkTx *tx;
336         int color;
337
338         g_return_if_fail(widget != NULL);
339         g_return_if_fail(GTK_IS_TX(widget));
340
341         tx = GTK_TX(widget);
342         
343         GtkAllocation allocation;
344         GdkRGBA midColor;
345         gboolean fg = (tx->current_fg == tx->audio_colors_focus);
346         
347         if (tx->audio_colors_focus) { 
348                 free(tx->audio_colors_focus); 
349                 free(tx->audio_colors_nofocus); 
350                 
351                 tx->audio_colors_focus = NULL; 
352                 tx->audio_colors_nofocus = NULL; 
353         } else {
354                 fg = FALSE;
355         }
356         
357         // update tx->yc
358         
359         gtk_widget_get_allocation(widget, &allocation);
360         tx->xc = allocation.width / 2;
361         tx->xmax = allocation.width;
362         tx->yc = allocation.height / 2;
363         tx->ymax = allocation.height;
364
365         // allocate colors
366         
367         tx->audio_colors_focus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
368         tx->audio_colors_nofocus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
369         
370         // no focus colors
371
372         midColor.red = tx->colors[COL_BG_NO_FOCUS].red + (tx->colors[COL_FG_NO_FOCUS].red - tx->colors[COL_BG_NO_FOCUS].red) / 4;
373         midColor.green = tx->colors[COL_BG_NO_FOCUS].green + (tx->colors[COL_FG_NO_FOCUS].green - tx->colors[COL_BG_NO_FOCUS].green) / 4;
374         midColor.blue = tx->colors[COL_BG_NO_FOCUS].blue + (tx->colors[COL_FG_NO_FOCUS].blue - tx->colors[COL_BG_NO_FOCUS].blue) / 4;
375         
376         for (color=0 ; color < tx->yc; color++) {
377                 float dist = (float) color / (float) tx->yc;
378                 
379                 tx->audio_colors_nofocus[color].red = midColor.red + dist*(tx->colors[COL_FG_NO_FOCUS].red - midColor.red);
380                 tx->audio_colors_nofocus[color].green = midColor.green + dist*(tx->colors[COL_FG_NO_FOCUS].green - midColor.green);
381                 tx->audio_colors_nofocus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_NO_FOCUS].blue - midColor.blue);
382                 tx->audio_colors_nofocus[color].alpha = 1.0;
383         }
384         // focus colors
385
386         midColor.red = tx->colors[COL_BG_FOCUS].red + (tx->colors[COL_FG_FOCUS].red - tx->colors[COL_BG_FOCUS].red) / 4;
387         midColor.green = tx->colors[COL_BG_FOCUS].green + (tx->colors[COL_FG_FOCUS].green - tx->colors[COL_BG_FOCUS].green) / 4;
388         midColor.blue = tx->colors[COL_BG_FOCUS].blue + (tx->colors[COL_FG_FOCUS].blue - tx->colors[COL_BG_FOCUS].blue) / 4;
389         
390         for (color=0 ; color < tx->yc; color++) {
391                 float dist = (float) color / (float) tx->yc;
392                 
393                 tx->audio_colors_focus[color].red = midColor.red + dist*(tx->colors[COL_FG_FOCUS].red - midColor.red);
394                 tx->audio_colors_focus[color].green = midColor.green + dist*(tx->colors[COL_FG_FOCUS].green - midColor.green);
395                 tx->audio_colors_focus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_FOCUS].blue - midColor.blue);
396                 tx->audio_colors_focus[color].alpha = 1.0;
397         }
398         
399         if (fg) {
400                 tx->current_fg = tx->audio_colors_focus;
401         } else {
402                 tx->current_fg = tx->audio_colors_nofocus;
403         }
404         
405         gtk_tx_reallocate_disp_data(widget);
406 }
407
408 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation) {
409         g_return_if_fail(widget != NULL);
410         g_return_if_fail(GTK_IS_TX(widget));
411         g_return_if_fail(allocation != NULL);
412
413         GtkTx *tx = GTK_TX(widget);
414
415         gtk_widget_set_allocation(widget, allocation);
416         
417         if (tx->render_buffer_surface_a) {
418                 cairo_surface_destroy(tx->render_buffer_surface_a);
419                 cairo_surface_destroy(tx->render_buffer_surface_b);
420                 tx->render_buffer_surface_a = NULL;
421                 tx->render_buffer_surface_b = NULL;
422         }
423         
424         tx->render_buffer_x_offset = -1;
425         tx->render_buffer_display_width = -1;
426         tx->render_buffer_fg = NULL;
427
428         if (gtk_widget_get_window(widget) != NULL) {
429                 tx->render_buffer_surface_a = gdk_window_create_similar_surface(gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation->width, allocation->height);
430                 tx->render_buffer_surface_b = gdk_window_create_similar_surface(gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation->width, allocation->height);
431         }
432         
433         tx->current_render_buffer_surface = tx->render_buffer_surface_a;
434         tx->previous_render_buffer_surface = tx->render_buffer_surface_b;
435
436         gtk_tx_prepare(widget);
437
438         if (gtk_widget_get_realized(widget)) {
439 #ifdef USE_DISPLAY_NORMALIZE            
440                 GtkTx *tx = GTK_TX(widget);
441                 tx->max_value=-1;
442 #endif          
443             gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height);
444         }
445 }
446
447 void gtk_tx_set_zoom(GtkTx *tx, f_prec zoom, int is_playing) {
448         GtkWidget *wid=GTK_WIDGET(tx);
449         
450         if (zoom != tx->zoom) {
451                 tx->zoom=zoom;
452                 gtk_tx_reallocate_disp_data(wid);
453                 if (!is_playing || (zoom < 0.01)) {
454                         gtk_widget_queue_draw(wid);
455                 }
456         }
457 }
458
459 #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); }
460 #define draw_sample(x, y1, y2, rgba) draw_line(x, y1, x, y2, rgba)
461 #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); }
462 #define draw_rectangle2(x, y, width, height, rgba) { gdk_cairo_set_source_rgba(cr, rgba); cairo_rectangle(cr, x, y, width, height); cairo_fill(cr); }
463
464 static void gtk_tx_update_render_buffer(GtkTx *tx) {
465         int x, src_x;
466         
467         if ((tx->render_buffer_display_width != tx->display_width) || 
468                         (tx->render_buffer_fg != tx->current_fg)) {
469                 // need to redraw all samples
470                 cairo_t *cr = cairo_create(tx->render_buffer_surface_a);
471                 tx->current_render_buffer_surface = tx->render_buffer_surface_a;
472                 tx->previous_render_buffer_surface = tx->render_buffer_surface_b;
473
474                 draw_rectangle2(0, 0, tx->xmax, tx->ymax, tx->current_bg);
475                 for (x=0, src_x=tx->display_x_offset; x < tx->xmax; x++, src_x++) {
476                         int dy = tx->disp_data[src_x];
477                         draw_sample(x, tx->yc-dy, tx->yc+dy+1, &tx->current_fg[dy]);
478                 }
479                 cairo_destroy(cr);
480
481                 tx->render_buffer_display_width = tx->display_width;
482                 tx->render_buffer_x_offset = tx->display_x_offset;
483                 tx->render_buffer_fg = tx->current_fg;
484    } else if (tx->render_buffer_x_offset != tx->display_x_offset) {
485                 // switch buffers
486                 cairo_surface_t *surface = tx->current_render_buffer_surface;
487                 tx->current_render_buffer_surface = tx->previous_render_buffer_surface;
488                 tx->previous_render_buffer_surface = surface;
489                 int motion = tx->render_buffer_x_offset - tx->display_x_offset;
490                 cairo_t *cr = cairo_create(tx->current_render_buffer_surface);
491
492                 int cur_x, start_x, stop_x;
493                 int width = tx->xmax - abs(motion);
494
495         if (motion > 0) {
496                         // move right
497                         cur_x = motion;
498                         start_x = 0;
499                         stop_x = motion;
500                 } else {
501                         // move left
502                         cur_x = 0;
503                         start_x = width;
504                         stop_x = tx->xmax;
505                 }
506     
507         cairo_set_source_surface(cr, tx->previous_render_buffer_surface, motion, 0);
508                 cairo_rectangle(cr, cur_x, 0, width, tx->ymax);
509                 cairo_fill(cr);
510
511                 // draw the rest;
512                 draw_rectangle2(start_x, 0, stop_x - start_x, tx->ymax, tx->current_bg);
513                 for (x=start_x, src_x=tx->display_x_offset+start_x; x < stop_x; x++, src_x++) {
514                         int dy = tx->disp_data[src_x];
515                         draw_sample(x, tx->yc-dy, tx->yc+dy+1, &tx->current_fg[dy]);
516                 }
517
518                 cairo_destroy(cr);
519
520                 tx->render_buffer_x_offset = tx->display_x_offset;
521         }
522 }
523
524 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr) {
525         GtkTx *tx;
526         gint x;
527         GdkRectangle area;
528         
529         g_return_val_if_fail(widget != NULL, FALSE);
530         g_return_val_if_fail(GTK_IS_TX(widget), FALSE);
531
532         tx = GTK_TX(widget);
533         
534         gdk_cairo_get_clip_rectangle(cr, &area);
535         
536         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
537         cairo_set_line_width(cr, 1);
538
539         if (tx->disp_data) {
540                 int step;
541                 int x_offset;
542
543                 if (tx->zoom > 0.0) {
544                     x_offset= tx->cursor_pos > tx->xc ? tx->cursor_pos-tx->xc : 0;
545                         if (x_offset+tx->xmax > tx->display_width) {
546                                 x_offset = tx->display_width-tx->xmax;
547                         }
548                 } else {
549                     x_offset = 0;                       
550                 }
551
552                 tx->cursor_x_pos = tx->cursor_pos-x_offset;
553                 tx->display_x_offset = x_offset;
554
555                 gtk_tx_update_render_buffer(tx);
556                 cairo_set_source_surface(cr, tx->current_render_buffer_surface, 0, 0);
557                 cairo_paint(cr);
558
559                 tx->cursor_history[tx->cursor_history_offset] = tx->cursor_pos;
560                 tx->cursor_history_offset++;
561                 if (tx->cursor_history_offset >= GTK_TX_HISTORY_LENGTH) {
562                         tx->cursor_history_offset -= GTK_TX_HISTORY_LENGTH;
563                 }
564
565                 if (globals.wav_display_history && !tx->mute) {
566                         int prev_sample_pos = -1;
567                         /* draw history */
568
569                         int max_dist = 1;
570                         for (step = 0; step < GTK_TX_HISTORY_LENGTH; step++) {
571                             max_dist = tX_max(max_dist, abs(tx->cursor_pos - tx->cursor_history[step]));
572                         }                       
573
574                         for (step = 0; step < GTK_TX_HISTORY_LENGTH; step++) {
575                                 int history_pos = tx->cursor_history_offset - step;
576
577                                 if (history_pos < 0) {
578                                         history_pos += GTK_TX_HISTORY_LENGTH;
579                                 }
580
581                                 int sample_pos = tx->cursor_history[history_pos];
582
583                                 if ((sample_pos >= 0) && (prev_sample_pos >= 0)) {
584                                         int sample_x_pos = sample_pos - tx->display_x_offset;
585                                         int prev_sample_x_pos = prev_sample_pos - tx->display_x_offset;
586                                         int min, max;
587
588                                         if (prev_sample_x_pos > sample_x_pos) {
589                                                 min = sample_x_pos;
590                                                 max = prev_sample_x_pos;
591                                         } else {
592                                                 min = prev_sample_x_pos;
593                                                 max = sample_x_pos;
594                                         }
595                                         
596                                         if ((max - min) <= tx->xc) {
597                                                 for (x=min; x < max; x++) {
598                                                         double scale = (1.0-(fabs(x-tx->cursor_x_pos) / (double) max_dist));
599                                                         int dist = scale * scale * 0.5 * (double) tx->ymax;
600                                                         int value = tx->disp_data[x+tx->display_x_offset] + dist;
601                                                         draw_sample(x, tx->yc-value, tx->yc+value+1, &tx->history_colors[GTK_TX_HISTORY_LENGTH-step]);
602                                                 }
603                                         } else {
604                                                 for (x = 0; x < min; x++) {
605                                                         double scale = (1.0-(fabs(x-tx->cursor_x_pos) / (double) max_dist));
606                                                         int dist = scale * scale * 0.5 * (double) tx->ymax;
607                                                         int value = tx->disp_data[x+tx->display_x_offset] + dist;
608                                                         draw_sample(x, tx->yc-value, tx->yc+value+1, &tx->history_colors[GTK_TX_HISTORY_LENGTH-step]);
609                                                 }
610                                                 for (x= max; x < tx->xmax; x++) {
611                                                         double scale = (1.0-(fabs(x-tx->cursor_x_pos) / (double) max_dist));
612                                                         int dist = scale * scale * 0.5 * (double) tx->ymax;
613                                                         int value = tx->disp_data[x+tx->display_x_offset] + dist;
614                                                         draw_sample(x, tx->yc-value, tx->yc+value+1, &tx->history_colors[GTK_TX_HISTORY_LENGTH-step]);
615                                                 }
616                                         }
617                                 }
618
619                                 prev_sample_pos = sample_pos;
620                         }
621                 }
622                 /* draw cursor */
623                 draw_sample(tx->cursor_x_pos, 0, tx->ymax, tx->mute ? &tx->colors[COL_CURSOR_MUTE] : &tx->colors[COL_CURSOR]);
624         } else {
625                 draw_rectangle(area, tx->current_bg);
626                 draw_line(area.x, tx->yc, area.width, tx->yc, tx->current_fg);
627         }
628         
629         return FALSE;
630 }
631
632 void gtk_tx_update_pos_display(GtkTx * tx, int sample, int mute) {
633         GtkWidget *widget = GTK_WIDGET(tx);
634         
635         tx->cursor_pos = sample / tx->spp;
636         tx->mute = mute;
637
638         gtk_widget_queue_draw(widget);
639 }
640
641 void gtk_tx_cleanup_pos_display(GtkTx * tx) {
642         GtkWidget *widget;
643         GtkAllocation allocation;
644         int step;
645
646         widget = GTK_WIDGET(tx);
647         gtk_widget_get_allocation(widget, &allocation);
648
649         tx->display_x_offset=0;
650         tx->cursor_pos=-1;
651         tx->cursor_x_pos=-1;
652
653         for (step = 0; step < GTK_TX_HISTORY_LENGTH; step++) {
654                 tx->cursor_history[step] = -1;
655         }
656         
657         gtk_widget_queue_draw(widget);
658 }
659
660 void gtk_tx_show_focus(GtkTx *tx, int show) {
661         if (show) {
662                 tx->current_fg=tx->audio_colors_focus;
663                 tx->current_bg=&tx->colors[COL_BG_FOCUS];
664         } else {
665                 tx->current_fg=tx->audio_colors_nofocus;
666                 tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
667         }
668         
669         gtk_widget_queue_draw(GTK_WIDGET(tx));  
670 }
671
672 f_prec gtk_tx_get_zoom(GtkTx *tx) {
673         return tx->zoom;
674 }