755f4e3fe8f1013cde865b016720a8bc1cd25a55
[terminatorX.git] / src / tX_widget.c
1 /*
2     terminatorX - realtime audio scratching software
3     Copyright (C) 1999-2002  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, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  
19     File: tX_widget.c
20  
21     Description: This contains the implementation of the tx_widget.
22                  This file is based on the GTK+ widget example from
23                  the GTK+ 1.2 tutorial.
24 */
25
26 #include <math.h>
27
28 #include <gtk/gtkwindow.h>
29 #include "tX_widget.h"
30 #include "tX_types.h"
31 #include "tX_global.h"
32 #include <malloc.h>
33
34 #ifndef WIN32
35 #include <unistd.h>
36 #endif
37
38 #define TX_DEFAULT_SIZE_X 100
39 #define TX_DEFAULT_SIZE_Y 30
40
41 /* forward declaration */
42 static void gtk_tx_class_init(GtkTxClass *);
43 static void gtk_tx_init(GtkTx * tx);
44 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples);
45 static void gtk_tx_destroy(GtkObject * object);
46 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples);
47 static void gtk_tx_realize(GtkWidget * widget);
48 static void gtk_tx_size_request(GtkWidget * widget, GtkRequisition * requisition);
49 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation);
50 static gint gtk_tx_expose(GtkWidget * widget, GdkEventExpose * event);
51 static void gtk_tx_update(GtkTx * tx);
52 static void gtk_tx_prepare(GtkWidget * widget);
53
54 /* data */
55 static GtkWidgetClass *parent_class = NULL;
56
57 /* widget "methods" */
58
59 guint gtk_tx_get_type() {
60         static guint tx_type = 0;
61         
62         if (!tx_type) {
63                 GtkTypeInfo tx_info = {
64                 "GtkTx",
65                 sizeof(GtkTx),
66                 sizeof(GtkTxClass),
67                 (GtkClassInitFunc) gtk_tx_class_init,
68                 (GtkObjectInitFunc) gtk_tx_init,
69                 /* reserved */ NULL,
70                 /* reserved */ NULL,
71                 /* reserved */ NULL
72         };
73         
74          tx_type = gtk_type_unique(gtk_widget_get_type(), &tx_info);
75         }
76         
77         return tx_type;
78 }
79
80 static void gtk_tx_class_init(GtkTxClass * gclass) {
81         GtkObjectClass *object_class;
82         GtkWidgetClass *widget_class;
83         
84         object_class = (GtkObjectClass *) gclass;
85         widget_class = (GtkWidgetClass *) gclass;
86         
87         parent_class = gtk_type_class(gtk_widget_get_type());
88         
89         object_class->destroy = gtk_tx_destroy;
90         
91         widget_class->realize = gtk_tx_realize;
92         widget_class->expose_event = gtk_tx_expose;
93         widget_class->size_request = gtk_tx_size_request;
94         widget_class->size_allocate = gtk_tx_size_allocate;
95 //      widget_class->button_press_event = gtk_tx_button_press;
96 //      widget_class->button_release_event = gtk_tx_button_release;
97 //      widget_class->motion_notify_event = gtk_tx_motion_notify;
98 }
99
100 void gtk_tx_mk_col(GtkTx * tx, GdkColor * col, float r, float g, float b) {
101         float max = 65535.0;
102         
103         col->red = (gint) (r * max);
104         col->green = (gint) (g * max);
105         col->blue = (gint) (b * max);
106                 
107         gdk_colormap_alloc_color(gtk_widget_get_colormap(GTK_WIDGET(tx)), col, 0, 1);
108 }
109
110 static void gtk_tx_init(GtkTx * tx) {
111         GdkColormap *priv;
112
113         tx->disp_data = NULL;
114         tx->data = NULL;
115         tx->samples = 0;
116         tx->do_showframe = 0;
117 #ifdef USE_DISPLAY_NORMALIZE
118         tx->max_value=-1;
119 #endif
120         
121         priv = gdk_colormap_new(gtk_widget_get_visual(GTK_WIDGET(tx)), 6);
122         gtk_widget_set_colormap(GTK_WIDGET(tx), priv);
123         
124         gtk_tx_mk_col(tx, &tx->bg, 0, 0, 0);
125         gtk_tx_mk_col(tx, &tx->fg, 0, 1, 0);
126         gtk_tx_mk_col(tx, &tx->focus_fg, 1, 1, 0);
127         gtk_tx_mk_col(tx, &tx->focus_bg, 0, 0, 0.3);    
128         
129         gtk_tx_mk_col(tx, &tx->busy, 1, 0.4, 0.4);
130         gtk_tx_mk_col(tx, &tx->mute, 1, 1, 1);
131         
132         tx->current_fg=&tx->fg;
133         tx->current_bg=&tx->bg;
134 }
135
136 GtkWidget *gtk_tx_new(int16_t * wavdata, int wavsamples) {
137         GtkTx *tx;
138
139         tx = gtk_type_new(gtk_tx_get_type());
140
141         tx->data = wavdata;
142         tx->samples = wavsamples;
143         tx->zoom=0;
144         tx->zoom_scale=1;
145         tx->display_x_offset=0;
146         
147         return GTK_WIDGET(tx);
148 }
149
150 static void gtk_tx_destroy(GtkObject * object) {
151         g_return_if_fail(object != NULL);
152         g_return_if_fail(GTK_IS_TX(object));
153
154         if (GTK_OBJECT_CLASS(parent_class)->destroy) {
155                 (*GTK_OBJECT_CLASS(parent_class)->destroy) (object);
156         }
157 }
158
159 #define MAX_ZOOM_WIDTH 500000.0
160
161 void gtk_tx_set_data(GtkTx * tx, int16_t * wavdata, int wavsamples) {
162         g_return_if_fail(tx != NULL);
163         g_return_if_fail(GTK_IS_TX(tx));
164
165         tx->data = wavdata;
166         tx->samples = wavsamples;
167 #ifdef USE_DISPLAY_NORMALIZE    
168         tx->max_value=-1;
169 #endif
170         
171         gtk_tx_prepare(GTK_WIDGET(tx));
172         gtk_tx_update(tx);
173 }
174
175 static void gtk_tx_realize(GtkWidget * widget) {
176         GtkTx *tx;
177         GdkWindowAttr attributes;
178         gint attributes_mask;
179
180         g_return_if_fail(widget != NULL);
181         g_return_if_fail(GTK_IS_TX(widget));
182
183         GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
184         tx = GTK_TX(widget);
185
186         attributes.x = widget->allocation.x;
187         attributes.y = widget->allocation.y;
188         attributes.width = widget->allocation.width;
189         attributes.height = widget->allocation.height;
190         attributes.wclass = GDK_INPUT_OUTPUT;
191         attributes.window_type = GDK_WINDOW_CHILD;
192         attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
193         attributes.visual = gtk_widget_get_visual(widget);
194         attributes.colormap = gtk_widget_get_colormap(widget);
195         attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
196
197         widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask);
198         widget->style = gtk_style_attach(widget->style, widget->window);
199
200         gdk_window_set_user_data(widget->window, widget);
201
202         gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL);
203 }
204
205 static void gtk_tx_size_request(GtkWidget * widget, GtkRequisition * requisition) {
206         requisition->width = TX_DEFAULT_SIZE_X;
207         requisition->height = TX_DEFAULT_SIZE_Y;
208 }
209
210 static void gtk_tx_prepare(GtkWidget * widget) {
211         int x, sample;
212         f_prec temp;
213         int16_t *ptr;
214         f_prec value;
215         GtkTx *tx;
216         int avg_pos;
217         double scale;
218         
219         g_return_if_fail(widget != NULL);
220         g_return_if_fail(GTK_IS_TX(widget));
221
222         tx = GTK_TX(widget);
223         
224         tx->yc = widget->allocation.height / 2;
225
226         if (tx->disp_data) free(tx->disp_data);
227
228         if (tx->data) {
229                 int max_spp=tx->samples/widget->allocation.width;
230                 int min_spp=tx->samples/MAX_ZOOM_WIDTH;
231                 gdouble diff;
232                 
233                 if (min_spp==0) min_spp=1;
234                 
235                 diff=max_spp-min_spp;
236                 
237                 tx->spp=min_spp+diff*(1.0-tx->zoom);
238                 tx->display_width = tx->samples/tx->spp;
239                 
240 #ifdef USE_DISPLAY_NORMALIZE    
241                 tx->max_value=-1;
242 #endif          
243                 
244             tx->disp_data = (int16_t *) malloc(tx->display_width * sizeof(int16_t));
245
246             if (tx->disp_data) {                        
247 #ifdef USE_DISPLAY_NORMALIZE            
248                         if (tx->max_value==-1) {
249                                 /* We haven't figured a max value yet... */
250                                 //puts("searching max...");
251                 
252                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
253                                         value = 0;
254                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
255                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
256                                         }
257                                         if (value>tx->max_value) tx->max_value=value;
258                                         tx->disp_data[x] = value; 
259                                 }
260                                 for (x = 0; x < tx->display_width; x++) {
261                                         f_prec t=tx->disp_data[x]/(double) tx->max_value;
262                                         tx->disp_data[x]=(int) (t * (f_prec) (tx->yc));
263                                 }
264                         } else {
265 #endif                          
266                                 //puts("have max...");
267                                 /* We have a max value... */
268                                 for (x = 0, ptr = tx->disp_data; x < tx->display_width; ptr++, x++) {
269                                         f_prec t;
270                                         value = 0;
271                                         for (sample = x * tx->spp, avg_pos=1; sample < (x + 1) * tx->spp; sample++) {
272                                                 value += (abs(tx->data[sample])-value)/(double) avg_pos++;
273                                         }
274 #ifdef USE_DISPLAY_NORMALIZE                                    
275                                         t=value/(double) tx->max_value;
276 #else
277                                         t=value/32768.0;
278 #endif                                  
279                                         tx->disp_data[x] = (int) (t * (f_prec) (tx->yc)); 
280                                 }
281 #ifdef USE_DISPLAY_NORMALIZE
282                         }
283 #endif                  
284                 }
285         } else {
286             tx->disp_data = NULL;
287         }
288         //tX_warning("spp: %i samples: %i width %i x %i", tx->spp, tx->samples, tx->display_width, x);
289 }
290
291 static void gtk_tx_size_allocate(GtkWidget * widget, GtkAllocation * allocation) {
292         GtkTx *tx;
293
294         g_return_if_fail(widget != NULL);
295         g_return_if_fail(GTK_IS_TX(widget));
296         g_return_if_fail(allocation != NULL);
297
298         widget->allocation = *allocation;
299
300         gtk_tx_prepare(widget);
301
302         if (GTK_WIDGET_REALIZED(widget)) {
303             tx = GTK_TX(widget);
304 #ifdef USE_DISPLAY_NORMALIZE            
305                 tx->max_value=-1;
306 #endif          
307             gdk_window_move_resize(widget->window, allocation->x, allocation->y, allocation->width, allocation->height);
308         }
309 }
310
311 #define min(a,b) ((a) < (b) ? (a) : (b))
312 #define max(a,b) ((a) > (b) ? (a) : (b))
313
314 static gint gtk_tx_expose(GtkWidget * widget, GdkEventExpose * event) {
315         GtkTx *tx;
316         gint x, pos;
317         GdkRectangle *area;
318         
319         g_return_val_if_fail(widget != NULL, FALSE);
320         g_return_val_if_fail(GTK_IS_TX(widget), FALSE);
321         g_return_val_if_fail(event != NULL, FALSE);
322
323 /*      if (event->count > 0) { 
324                 fprintf(stderr, "Ignoring %i expose events.\n", event->count);
325                 return FALSE;
326         }*/
327
328         area=&event->area;
329
330         tx = GTK_TX(widget);
331
332         gdk_gc_set_foreground(widget->style->fg_gc[widget->state], tx->current_bg);
333
334         gdk_draw_rectangle(widget->window,
335                 widget->style->fg_gc[widget->state], 1, 
336                 area->x, area->y,
337                 area->width, area->height);
338
339         gdk_gc_set_foreground(widget->style->fg_gc[widget->state], tx->current_fg);
340
341         if (tx->disp_data) {
342                 int max_x=area->x+area->width;
343
344             for (x =area->x; x < max_x; x++) {
345                         gdk_draw_line(widget->window,
346                                 widget->style->fg_gc[widget->state], x,
347                                 tx->yc - tx->disp_data[tx->display_x_offset+x], x,
348                                 tx->yc + tx->disp_data[tx->display_x_offset+x]);
349             }
350         } else {
351             gdk_draw_line(widget->window, widget->style->fg_gc[widget->state],
352                          0, tx->yc, widget->allocation.width, tx->yc);
353         }
354
355         return FALSE;
356 }
357
358 void gtk_tx_set_zoom(GtkTx *tx, f_prec zoom) {
359         GtkWidget *wid=GTK_WIDGET(tx);
360         
361         tx->zoom=zoom;
362         gtk_tx_prepare(wid);
363         gtk_widget_queue_draw_area(wid, 0, 0, wid->allocation.width, wid->allocation.height);
364 }
365
366 static void gtk_tx_update(GtkTx * tx) {
367         g_return_if_fail(tx != NULL);
368         g_return_if_fail(GTK_IS_TX(tx));
369
370         gtk_widget_draw(GTK_WIDGET(tx), NULL);
371 }
372
373 void gtk_tx_prepare_pos_display(GtkTx * tx) {
374         tx->cursor_pos=-1;
375 }
376
377 void gtk_tx_update_pos_display(GtkTx * tx, int sample, int mute) {
378         GtkWidget *widget;
379         GdkWindow *window;
380         GdkGC *gc;
381
382         int current_x, x, y, yc, ymax, tmp;
383         int current_pos, current_pos_x, x_offset;
384         int force_draw=0;
385
386         /* Don't update if not required */
387
388         //current_x = sample / tx->spp + FR_SIZE;
389         current_pos = sample / tx->spp;
390         
391         if ((current_pos == tx->cursor_pos) && (tx->lastmute == mute)) return;
392         tx->lastmute = mute;
393
394         /* speedup + easyness */
395
396         widget = GTK_WIDGET(tx);
397         window = widget->window;
398
399         gc = widget->style->fg_gc[widget->state];
400         yc = tx->yc;
401         ymax = widget->allocation.height-1;
402
403         /* clean up last pos */
404
405         x = tx->cursor_x_pos;
406         
407         if (x >= 0) {
408             gdk_gc_set_foreground(gc, tx->current_bg);
409             gdk_draw_line(window, gc, x, 0, x, ymax);
410
411             gdk_gc_set_foreground(gc, tx->current_fg);
412             y = tx->disp_data[x+tx->display_x_offset];
413             gdk_draw_line(window, gc, x, yc + y, x, yc - y);
414         }
415         
416         /* compute new position */
417         if (tx->zoom==0) {
418                 current_pos_x=current_pos;
419                 x_offset=0;             
420         } else {                
421                 tmp=widget->allocation.width/2+1;
422                 
423                 if (current_pos>tmp) {
424                         x_offset=current_pos-tmp;
425                         
426                         if (x_offset+widget->allocation.width>=tx->display_width) {
427                                 x_offset=tx->display_width-widget->allocation.width;
428                         }
429                         
430                         current_pos_x=current_pos-x_offset;
431                 } else {
432                         x_offset=0;
433                         current_pos_x=current_pos;
434                 }
435                 
436                 if (x_offset!=tx->display_x_offset) {
437                         int x_move=tx->display_x_offset-x_offset;
438                         
439                         if (abs(x_move)<widget->allocation.width) {
440                                 gdk_window_scroll(window, x_move, 0);
441                         } else {
442                                 /* we've moved so far that there's nothing to keep from our current display */
443                                 force_draw=1;
444                         }
445                 }
446         }
447         
448         /* store current_pos */
449
450         tx->cursor_pos = current_pos;
451         tx->cursor_x_pos = current_pos_x;
452         tx->display_x_offset = x_offset;
453         
454         /* not drawing current pos - let expose() take care of this... */
455
456         x = current_pos_x;
457
458         if (mute) gdk_gc_set_foreground(gc, &tx->mute);
459         else gdk_gc_set_foreground(gc, &tx->busy);
460
461         gdk_draw_line(window, gc, x, 0, x, ymax);
462         
463         if (force_draw) {
464                 gtk_widget_queue_draw_area(widget, 0, 0, widget->allocation.width, widget->allocation.height);
465         }
466 }
467
468 void gtk_tx_cleanup_pos_display(GtkTx * tx) {
469         GtkWidget *widget;
470         GdkWindow *window;
471         GdkGC *gc;
472         int x, y, ymax, yc;
473
474         widget = GTK_WIDGET(tx);
475         window = widget->window;
476         gc = widget->style->fg_gc[widget->state];
477         yc = tx->yc;
478         ymax = widget->allocation.height - 1;
479
480         tx->display_x_offset=0;
481         tx->cursor_pos=-1;
482         tx->cursor_x_pos=-1;
483         tx->do_showframe=0;
484         //tx->current_fg=&tx->fg;
485         
486         gtk_widget_queue_draw(widget);
487 }
488
489 void gtk_tx_show_frame(GtkTx *tx, int show) {
490         if (show) {
491                 tx->current_fg=&tx->focus_fg;
492                 tx->current_bg=&tx->focus_bg;
493         } else {
494                 tx->current_fg=&tx->fg;
495                 tx->current_bg=&tx->bg;
496         }
497         
498         gtk_widget_queue_draw(GTK_WIDGET(tx));  
499 }