2aed4560d5f793fd7f82a11b4ba5dcf193def0fb
[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_update(GtkTx * tx);
56 static void gtk_tx_prepare(GtkWidget * widget);
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         
90         parent_class = (GtkWidgetClass *) g_type_class_peek(gtk_widget_get_type());
91         
92         widget_class->destroy = gtk_tx_destroy;
93         
94         widget_class->realize = gtk_tx_realize;
95         widget_class->draw = gtk_tx_draw;
96         widget_class->get_preferred_height = gtk_tx_get_preferred_height;
97         widget_class->get_preferred_width = gtk_tx_get_preferred_width;
98         widget_class->size_allocate = gtk_tx_size_allocate;
99 //      widget_class->button_press_event = gtk_tx_button_press;
100 //      widget_class->button_release_event = gtk_tx_button_release;
101 //      widget_class->motion_notify_event = gtk_tx_motion_notify;
102 }
103
104 #define COL_BG_FOCUS     0
105 #define COL_BG_NO_FOCUS  1
106 #define COL_FG_FOCUS     2
107 #define COL_FG_NO_FOCUS  3
108 #define COL_CURSOR       4
109 #define COL_CURSOR_MUTE  5
110
111 void gtk_tx_update_colors(GtkTx *tx)
112 {
113         gdk_rgba_parse(&tx->colors[COL_BG_FOCUS], globals.wav_display_bg_focus);
114         tx->colors[COL_BG_FOCUS].alpha=1.0;
115         gdk_rgba_parse(&tx->colors[COL_BG_NO_FOCUS], globals.wav_display_bg_no_focus);
116         tx->colors[COL_BG_NO_FOCUS].alpha=1.0;
117         
118         gdk_rgba_parse(&tx->colors[COL_FG_FOCUS], globals.wav_display_fg_focus);
119         tx->colors[COL_FG_FOCUS].alpha=1.0;
120         gdk_rgba_parse(&tx->colors[COL_FG_NO_FOCUS], globals.wav_display_fg_no_focus);
121         tx->colors[COL_FG_NO_FOCUS].alpha=1.0;
122         
123         gdk_rgba_parse(&tx->colors[COL_CURSOR], globals.wav_display_cursor);
124         tx->colors[COL_CURSOR].alpha=1.0;
125         gdk_rgba_parse(&tx->colors[COL_CURSOR_MUTE], globals.wav_display_cursor_mute);
126         tx->colors[COL_CURSOR_MUTE].alpha=1.0;
127 }
128
129
130 static void gtk_tx_init(GtkTx * tx) {
131         tx->disp_data = NULL;
132         tx->data = NULL;
133         tx->samples = 0;
134         tx->do_showframe = 0;
135 #ifdef USE_DISPLAY_NORMALIZE
136         tx->max_value=-1;
137 #endif
138         
139         memset(tx->colors, 0, sizeof(tx->colors));
140         
141         gtk_tx_update_colors(tx);
142         
143         tx->current_fg=tx->audio_colors_focus;
144         tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
145         
146         tx->audio_colors_focus = NULL;
147         tx->audio_colors_nofocus = NULL;
148         
149         tx->spp=1;
150         tx->lastmute=-1;
151         tx->zoom=0;
152         tx->cursor_pos=0;
153         tx->cursor_x_pos=0;
154         
155         tx->surface = NULL;
156 }
157
158 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples) {
159         GtkTx *tx;
160
161         tx = (GtkTx *) g_object_new(gtk_tx_get_type (), NULL);
162
163         tx->data = wavdata;
164         tx->samples = wavsamples;
165         tx->zoom=0;
166         tx->display_x_offset=0;
167         
168         return GTK_WIDGET(tx);
169 }
170
171 static void gtk_tx_destroy(GtkWidget * widget) {
172         GtkTx *tx;
173         g_return_if_fail(widget != NULL);
174         g_return_if_fail(GTK_IS_TX(widget));
175
176         tx=GTK_TX(widget);
177         
178         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
179         
180         if (GTK_WIDGET_CLASS(parent_class)->destroy) {
181                 (*GTK_WIDGET_CLASS(parent_class)->destroy) (widget);
182         }       
183 }
184
185 #define MAX_ZOOM_WIDTH 500000.0
186
187 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples) {
188         g_return_if_fail(tx != NULL);
189         g_return_if_fail(GTK_IS_TX(tx));
190
191         tx->data = wavdata;
192         tx->samples = wavsamples;
193 #ifdef USE_DISPLAY_NORMALIZE    
194         tx->max_value=-1;
195 #endif
196         
197         gtk_tx_prepare(GTK_WIDGET(tx));
198         gtk_tx_update(tx);
199 }
200
201 static void gtk_tx_realize(GtkWidget * widget) {
202         GdkWindowAttr attributes;
203         gint attributes_mask;
204         GtkTx *tx;
205         
206         g_return_if_fail(widget != NULL);
207         g_return_if_fail(GTK_IS_TX(widget));
208
209         tx = GTK_TX(widget);
210         gtk_widget_set_realized(widget, TRUE);
211
212         GtkAllocation allocation;
213         gtk_widget_get_allocation(widget, &allocation);
214         attributes.x = allocation.x;
215         attributes.y = allocation.y;
216         attributes.width = allocation.width;
217         attributes.height = allocation.height;
218         attributes.wclass = GDK_INPUT_OUTPUT;
219         attributes.window_type = GDK_WINDOW_CHILD;
220         attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
221         attributes.visual = gtk_widget_get_visual(widget);
222         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
223
224         gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask));
225
226         gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
227
228         if (tx->surface) {
229                 cairo_surface_destroy (tx->surface);
230         }
231         
232         tx->surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation.width, allocation.height);
233 }
234
235 static void gtk_tx_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
236 {
237   *minimal_width = *natural_width = TX_DEFAULT_SIZE_X;
238 }
239
240 static void gtk_tx_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
241 {
242     *minimal_height = *natural_height = TX_DEFAULT_SIZE_Y;
243 }
244
245 static void gtk_tx_reallocate_disp_data(GtkWidget *widget) {
246         GtkAllocation allocation;
247         GtkTx *tx = GTK_TX(widget);
248         int x, sample, avg_pos;
249         int16_t *ptr;
250         f_prec value;
251
252         gtk_widget_get_allocation(widget, &allocation);
253
254         if (tx->disp_data) { free(tx->disp_data); tx->disp_data=NULL; }
255
256         if (tx->data) {
257                 int max_spp=tx->samples/allocation.width;
258                 int min_spp=tx->samples/MAX_ZOOM_WIDTH;
259                 gdouble diff;
260                 
261                 if (min_spp==0) min_spp=1;
262                 
263                 diff=max_spp-min_spp;
264                 
265                 tx->spp=min_spp+diff*(1.0-tx->zoom);
266                 tx->display_width = tx->samples/tx->spp;
267                 
268 #ifdef USE_DISPLAY_NORMALIZE    
269                 tx->max_value=-1;
270 #endif          
271                 
272             tx->disp_data = (int16_t *) malloc(tx->display_width * sizeof(int16_t));
273
274             if (tx->disp_data) {                        
275 #ifdef USE_DISPLAY_NORMALIZE            
276                         if (tx->max_value==-1) {
277                                 /* We haven't figured a max value yet... */
278                                 //puts("searching max...");
279                 
280                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
281                                         value = 0;
282                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
283                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
284                                         }
285                                         if (value>tx->max_value) tx->max_value=value;
286                                         tx->disp_data[x] = value; 
287                                 }
288                                 for (x = 0; x < tx->display_width; x++) {
289                                         f_prec t=tx->disp_data[x]/(double) tx->max_value;
290                                         tx->disp_data[x]=(int) (t * (f_prec) (tx->yc));
291                                 }
292                         } else {
293 #endif                          
294                                 //puts("have max...");
295                                 /* We have a max value... */
296                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
297                                         f_prec t;
298                                         value = 0;
299                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
300                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
301                                         }
302 #ifdef USE_DISPLAY_NORMALIZE                                    
303                                         t=value/(double) tx->max_value;
304 #else
305                                         t=value/32768.0;
306 #endif                                  
307                                         tx->disp_data[x] = (int) (t * (f_prec) (tx->yc)); 
308                                 }
309 #ifdef USE_DISPLAY_NORMALIZE
310                         }
311 #endif                  
312                 }
313         } else {
314             tx->disp_data = NULL;
315         }
316         
317 }
318
319
320
321 static void gtk_tx_prepare(GtkWidget * widget) {
322         GtkTx *tx;
323         int color;
324
325         g_return_if_fail(widget != NULL);
326         g_return_if_fail(GTK_IS_TX(widget));
327
328         tx = GTK_TX(widget);
329         
330         GtkAllocation allocation;
331         GdkRGBA midColor;
332         gboolean fg = (tx->current_fg == tx->audio_colors_focus);
333         
334         if (tx->audio_colors_focus) { 
335                 free(tx->audio_colors_focus); 
336                 free(tx->audio_colors_nofocus); 
337                 
338                 tx->audio_colors_focus = NULL; 
339                 tx->audio_colors_nofocus = NULL; 
340         } else {
341                 fg = FALSE;
342         }
343         
344         // update tx->yc
345         
346         gtk_widget_get_allocation(widget, &allocation);
347         tx->yc = allocation.height / 2;
348         
349         // allocate colors
350         
351         tx->audio_colors_focus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
352         tx->audio_colors_nofocus = (GdkRGBA *) malloc(tx->yc * sizeof(GdkRGBA));
353         
354         // no focus colors
355
356         midColor.red = tx->colors[COL_BG_NO_FOCUS].red + (tx->colors[COL_FG_NO_FOCUS].red - tx->colors[COL_BG_NO_FOCUS].red) / 4;
357         midColor.green = tx->colors[COL_BG_NO_FOCUS].green + (tx->colors[COL_FG_NO_FOCUS].green - tx->colors[COL_BG_NO_FOCUS].green) / 4;
358         midColor.blue = tx->colors[COL_BG_NO_FOCUS].blue + (tx->colors[COL_FG_NO_FOCUS].blue - tx->colors[COL_BG_NO_FOCUS].blue) / 4;
359         
360         for (color=0 ; color < tx->yc; color++) {
361                 float dist = (float) color / (float) tx->yc;
362                 
363                 tx->audio_colors_nofocus[color].red = midColor.red + dist*(tx->colors[COL_FG_NO_FOCUS].red - midColor.red);
364                 tx->audio_colors_nofocus[color].green = midColor.green + dist*(tx->colors[COL_FG_NO_FOCUS].green - midColor.green);
365                 tx->audio_colors_nofocus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_NO_FOCUS].blue - midColor.blue);
366                 tx->audio_colors_nofocus[color].alpha = 1.0;
367         }
368         // focus colors
369
370         midColor.red = tx->colors[COL_BG_FOCUS].red + (tx->colors[COL_FG_FOCUS].red - tx->colors[COL_BG_FOCUS].red) / 4;
371         midColor.green = tx->colors[COL_BG_FOCUS].green + (tx->colors[COL_FG_FOCUS].green - tx->colors[COL_BG_FOCUS].green) / 4;
372         midColor.blue = tx->colors[COL_BG_FOCUS].blue + (tx->colors[COL_FG_FOCUS].blue - tx->colors[COL_BG_FOCUS].blue) / 4;
373         
374         for (color=0 ; color < tx->yc; color++) {
375                 float dist = (float) color / (float) tx->yc;
376                 
377                 tx->audio_colors_focus[color].red = midColor.red + dist*(tx->colors[COL_FG_FOCUS].red - midColor.red);
378                 tx->audio_colors_focus[color].green = midColor.green + dist*(tx->colors[COL_FG_FOCUS].green - midColor.green);
379                 tx->audio_colors_focus[color].blue = midColor.blue + dist*(tx->colors[COL_FG_FOCUS].blue - midColor.blue);
380                 tx->audio_colors_focus[color].alpha = 1.0;
381         }
382         
383         if (fg) {
384                 tx->current_fg = tx->audio_colors_focus;
385         } else {
386                 tx->current_fg = tx->audio_colors_nofocus;
387         }
388         
389         tx->cursor_pos=-1;
390         tx->lastmute=-1;
391
392         gtk_tx_reallocate_disp_data(widget);
393         //tX_warning("spp: %i samples: %i width %i x %i", tx->spp, tx->samples, tx->display_width, x);
394 }
395
396 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation) {
397         g_return_if_fail(widget != NULL);
398         g_return_if_fail(GTK_IS_TX(widget));
399         g_return_if_fail(allocation != NULL);
400
401         gtk_widget_set_allocation(widget, allocation);
402
403         gtk_tx_prepare(widget);
404
405         if (gtk_widget_get_realized(widget)) {
406 #ifdef USE_DISPLAY_NORMALIZE            
407             GtkTx *tx = GTK_TX(widget);
408                 tx->max_value=-1;
409 #endif          
410             gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height);
411         }
412 }
413
414 #define min(a,b) ((a) < (b) ? (a) : (b))
415 #define max(a,b) ((a) > (b) ? (a) : (b))
416
417 static gboolean gtk_tx_draw(GtkWidget * widget, cairo_t *cr) {
418         GtkTx *tx;
419         gint x;
420         GdkRectangle area;
421         
422         g_return_val_if_fail(widget != NULL, FALSE);
423         g_return_val_if_fail(GTK_IS_TX(widget), FALSE);
424
425         gdk_cairo_get_clip_rectangle(cr, &area);
426
427         tx = GTK_TX(widget);
428         
429         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
430         cairo_set_source_surface (cr, tx->surface, 0, 0);
431         cairo_set_line_width(cr,1);
432
433         gdk_cairo_set_source_rgba (cr, tx->current_bg);
434
435         cairo_rectangle(cr, area.x, area.y, area.width, area.height);
436         cairo_fill(cr);
437
438         if (tx->disp_data) {
439                 int max_x=area.x+area.width+1;
440
441             for (x =area.x; x < max_x; x++) {
442                         int dy = tx->disp_data[tx->display_x_offset+x];
443                         gdk_cairo_set_source_rgba (cr, &tx->current_fg[dy]);
444                         cairo_move_to (cr, x, tx->yc - dy);
445                         cairo_line_to (cr, x, tx->yc + dy+1);
446                         cairo_stroke (cr);
447             }
448         } else {
449                 GtkAllocation allocation;
450                 gtk_widget_get_allocation(widget, &allocation);
451                 
452                 gdk_cairo_set_source_rgba (cr, tx->current_fg);
453                 cairo_move_to (cr, 0, tx->yc);
454                 cairo_line_to (cr, allocation.width, tx->yc);
455                 cairo_stroke (cr);
456         }
457         
458         return FALSE;
459 }
460
461 void gtk_tx_set_zoom(GtkTx *tx, f_prec zoom, int is_playing) {
462         GtkWidget *wid=GTK_WIDGET(tx);
463         
464         if (zoom != tx->zoom) {
465                 tx->zoom=zoom;
466                 gtk_tx_reallocate_disp_data(wid);
467                 if (!is_playing || (zoom < 0.01)) {
468                         gtk_widget_queue_draw(wid);
469                 }
470         }
471 }
472
473 static void gtk_tx_update(GtkTx * tx) {
474         g_return_if_fail(tx != NULL);
475         g_return_if_fail(GTK_IS_TX(tx));
476
477         gtk_widget_queue_draw(GTK_WIDGET(tx));
478 }
479
480 void gtk_tx_update_pos_display(GtkTx * tx, int sample, int mute) {
481         GtkWidget *widget;
482         GdkWindow *window;
483         cairo_t *cr;
484         
485         int x, y, yc, ymax, tmp;
486         int current_pos, current_pos_x, x_offset;
487         int force_draw=0;
488
489         /* Don't update if not required */
490
491         //current_x = sample / tx->spp + FR_SIZE;
492         current_pos = sample / tx->spp;
493         
494         if ((current_pos == tx->cursor_pos) && 
495                 (tx->lastmute == mute)) return;
496         tx->lastmute = mute;
497
498         /* speedup + easyness */
499
500         widget = GTK_WIDGET(tx);
501         window = gtk_widget_get_window(widget);
502
503         yc = tx->yc;
504         GtkAllocation allocation;
505         gtk_widget_get_allocation(widget, &allocation);
506         ymax = allocation.height-1;
507
508         /* clean up last pos */
509         
510         x = tx->cursor_x_pos;
511         
512         cr = gdk_cairo_create (gtk_widget_get_window(widget));
513         cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
514         cairo_set_source_surface (cr, tx->surface, 0, 0);
515         cairo_set_line_width(cr,1);
516         
517         if (x >= 0) {
518                 gdk_cairo_set_source_rgba (cr, tx->current_bg);
519                 
520                 cairo_move_to (cr, x, 0);
521                 cairo_line_to (cr, x, ymax);
522                 cairo_stroke (cr);
523                 
524             y = tx->disp_data[x+tx->display_x_offset];
525             gdk_cairo_set_source_rgba (cr, &tx->current_fg[y]);
526             
527                 cairo_move_to (cr, x, yc + y);
528                 cairo_line_to (cr, x, yc - y+1);
529                 cairo_stroke (cr);
530         }
531         
532         /* compute new position */
533         if (tx->zoom==0) {
534                 current_pos_x=current_pos;
535                 x_offset=0;             
536         } else {                
537                 tmp=allocation.width/2+1;
538                 
539                 if (current_pos>tmp) {
540                         x_offset=current_pos-tmp;
541                         
542                         if (x_offset+allocation.width>=tx->display_width) {
543                                 x_offset=tx->display_width-allocation.width;
544                         }
545                         
546                         current_pos_x=current_pos-x_offset;
547                 } else {
548                         x_offset=0;
549                         current_pos_x=current_pos;
550                 }
551                 
552                 if (x_offset!=tx->display_x_offset) {
553                         int x_move=tx->display_x_offset-x_offset;
554                         
555                         if (abs(x_move)<allocation.width) {
556                                 gdk_window_scroll(window, x_move, 0);
557                         } else {
558                                 /* we've moved so far that there's nothing to keep from our current display */
559                                 force_draw=1;
560                         }
561                 }
562         }
563         
564         /* store current_pos */
565
566         tx->cursor_pos = current_pos;
567         tx->cursor_x_pos = current_pos_x;
568         tx->display_x_offset = x_offset;
569         
570         /* not drawing current pos - let expose() take care of this... */
571
572         x = current_pos_x;
573
574         if (mute) gdk_cairo_set_source_rgba(cr, &tx->colors[COL_CURSOR_MUTE]);
575         else gdk_cairo_set_source_rgba(cr, &tx->colors[COL_CURSOR]);
576
577         cairo_move_to (cr, x, 0);
578         cairo_line_to (cr, x, ymax);
579         cairo_stroke (cr);
580         
581         cairo_destroy (cr);
582         
583         if (force_draw) {
584                 gtk_widget_queue_draw_area(widget, 0, 0, allocation.width, allocation.height);
585         }
586 }
587
588 void gtk_tx_cleanup_pos_display(GtkTx * tx) {
589         GtkWidget *widget;
590         GtkAllocation allocation;
591
592         widget = GTK_WIDGET(tx);
593         gtk_widget_get_allocation(widget, &allocation);
594
595         tx->display_x_offset=0;
596         tx->cursor_pos=-1;
597         tx->cursor_x_pos=-1;
598         tx->do_showframe=0;
599         //tx->current_fg=&tx->fg;
600         
601         gtk_widget_queue_draw(widget);
602 }
603
604 void gtk_tx_show_frame(GtkTx *tx, int show) {
605         if (show) {
606                 tx->current_fg=tx->audio_colors_focus;
607                 tx->current_bg=&tx->colors[COL_BG_FOCUS];
608         } else {
609                 tx->current_fg=tx->audio_colors_nofocus;
610                 tx->current_bg=&tx->colors[COL_BG_NO_FOCUS];
611         }
612         
613         gtk_widget_queue_draw(GTK_WIDGET(tx));  
614 }
615
616 f_prec gtk_tx_get_zoom(GtkTx *tx) {
617         return tx->zoom;
618 }