From 2c77e39e5341d57b0b9f4101fcfbe3e5cf28547a Mon Sep 17 00:00:00 2001 From: Rob Date: Sat, 19 Dec 2015 16:47:19 +0100 Subject: [PATCH 01/25] GTK+ 3 upgrade. --- Makefile | 6 +- interface.c | 1757 ++++++++++++++++++++++++++------------------------- 2 files changed, 896 insertions(+), 867 deletions(-) diff --git a/Makefile b/Makefile index d0d40a9..195dc6d 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ -VERSION = 0.2.3 +VERSION = 0.3 CC = gcc -CFLAGS = -Wall -O3 -DVERSION='"$(VERSION)"' `pkg-config --cflags gtk+-2.0 portaudio-2.0 fftw3f` -LDFLAGS = -lm `pkg-config --libs gtk+-2.0 portaudio-2.0 fftw3f` +CFLAGS = -Wall -O3 -DVERSION='"$(VERSION)"' `pkg-config --cflags gtk+-3.0 portaudio-2.0 fftw3f` +LDFLAGS = -lm `pkg-config --libs gtk+-3.0 portaudio-2.0 fftw3f` CFILES = interface.c algo.c audio.c HFILES = tg.h diff --git a/interface.c b/interface.c index d99bb76..5099999 100644 --- a/interface.c +++ b/interface.c @@ -1,20 +1,20 @@ /* - tg - Copyright (C) 2015 Marcello Mamino - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + tg + Copyright (C) 2015 Marcello Mamino + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ #include "tg.h" #include @@ -26,1005 +26,1034 @@ cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow; void print_debug(char *format,...) { - va_list args; - va_start(args,format); - vfprintf(stderr,format,args); - va_end(args); + va_list args; + va_start(args,format); + vfprintf(stderr,format,args); + va_end(args); } void error(char *format,...) { - char s[100]; - va_list args; - - va_start(args,format); - int size = vsnprintf(s,100,format,args); - va_end(args); - - char *t; - if(size < 100) { - t = s; - } else { - t = alloca(size+1); - va_start(args,format); - vsnprintf(t,size+1,format,args); - va_end(args); - } - - fprintf(stderr,"%s\n",t); - - GtkWidget *dialog = gtk_message_dialog_new(NULL,0,GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,"%s",t); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); + char s[100]; + va_list args; + + va_start(args,format); + int size = vsnprintf(s,100,format,args); + va_end(args); + + char *t; + if(size < 100) { + t = s; + } else { + t = alloca(size+1); + va_start(args,format); + vsnprintf(t,size+1,format,args); + va_end(args); + } + + fprintf(stderr,"%s\n",t); + + GtkWidget *dialog = gtk_message_dialog_new(NULL,0,GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,"%s",t); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); } void define_color(cairo_pattern_t **gc,double r,double g,double b) { - *gc = cairo_pattern_create_rgb(r,g,b); + *gc = cairo_pattern_create_rgb(r,g,b); } void initialize_palette() { - define_color(&black,0,0,0); - define_color(&white,1,1,1); - define_color(&red,1,0,0); - define_color(&green,0,0.8,0); - define_color(&blue,0,0,1); - define_color(&blueish,0,0,.5); - define_color(&yellow,1,1,0); + define_color(&black,0,0,0); + define_color(&white,1,1,1); + define_color(&red,1,0,0); + define_color(&green,0,0.8,0); + define_color(&blue,0,0,1); + define_color(&blueish,0,0,.5); + define_color(&yellow,1,1,0); } struct main_window { - GtkWidget *window; - GtkWidget *bph_combo_box; - GtkWidget *la_spin_button; - GtkWidget *output_drawing_area; - GtkWidget *tic_drawing_area; - GtkWidget *toc_drawing_area; - GtkWidget *period_drawing_area; - GtkWidget *paperstrip_drawing_area; + GtkWidget *window; + GtkWidget *bph_combo_box; + GtkWidget *la_spin_button; + GtkWidget *info_drawing_area; + GtkWidget *tic_drawing_area; + GtkWidget *toc_drawing_area; + GtkWidget *period_drawing_area; + GtkWidget *paperstrip_drawing_area; #ifdef DEBUG - GtkWidget *debug_drawing_area; + GtkWidget *debug_drawing_area; #endif - - struct processing_buffers *bfs; - struct processing_buffers *old; - - int bph; - int guessed_bph; - int last_bph; - double la; - double sample_rate; - - uint64_t *events; - int events_wp; - uint64_t events_from; - double trace_centering; - - int signal; + + struct processing_buffers *bfs; + struct processing_buffers *old; + + int bph; + int guessed_bph; + int last_bph; + double la; + double sample_rate; + + uint64_t *events; + int events_wp; + uint64_t events_from; + double trace_centering; + + int signal; }; void redraw(struct main_window *w) { - gtk_widget_queue_draw(w->output_drawing_area); - gtk_widget_queue_draw(w->tic_drawing_area); - gtk_widget_queue_draw(w->toc_drawing_area); - gtk_widget_queue_draw(w->period_drawing_area); - gtk_widget_queue_draw(w->paperstrip_drawing_area); + gtk_widget_queue_draw(w->info_drawing_area); + gtk_widget_queue_draw(w->tic_drawing_area); + gtk_widget_queue_draw(w->toc_drawing_area); + gtk_widget_queue_draw(w->period_drawing_area); + gtk_widget_queue_draw(w->paperstrip_drawing_area); #ifdef DEBUG - gtk_widget_queue_draw(w->debug_drawing_area); + gtk_widget_queue_draw(w->debug_drawing_area); #endif } /* Find the preset bph value closest corresponding to the current period */ int guess_bph(double period) { - double bph = 7200 / period; - double min = bph; - int i,ret; - - ret = 0; - for(i=0; preset_bph[i]; i++) { - double diff = fabs(bph - preset_bph[i]); - if(diff < min) { - min = diff; - ret = i; - } - } - - return preset_bph[ret]; + double bph = 7200 / period; + double min = bph; + int i,ret; + + ret = 0; + for(i=0; preset_bph[i]; i++) { + double diff = fabs(bph - preset_bph[i]); + if(diff < min) { + min = diff; + ret = i; + } + } + + return preset_bph[ret]; } struct processing_buffers *get_data(struct main_window *w, int *old) { - struct processing_buffers *p = w->bfs; - int i; - for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); - if(i>=0) { - if(w->old) pb_destroy_clone(w->old); - w->old = pb_clone(&p[i]); - *old = 0; - return &p[i]; - } else { - *old = 1; - return w->old; - } + struct processing_buffers *p = w->bfs; + int i; + for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); + if(i>=0) { + if(w->old) pb_destroy_clone(w->old); + w->old = pb_clone(&p[i]); + *old = 0; + return &p[i]; + } else { + *old = 1; + return w->old; + } } void recompute(struct main_window *w) { - w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); - int old; - struct processing_buffers *p = get_data(w,&old); - if(old) w->signal = -w->signal; - if(p) - w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); + w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); + int old; + struct processing_buffers *p = get_data(w,&old); + if(old) w->signal = -w->signal; + if(p) + w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); } +/* Called 10 times/second to keep the UI updated */ guint refresh_window(struct main_window *w) { - recompute(w); - redraw(w); - return TRUE; + recompute(w); + redraw(w); + return TRUE; } gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { - return FALSE; + return FALSE; } +/* Draw the actual waveform */ void draw_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) { - GtkAllocation temp; - gtk_widget_get_allocation (da, &temp); - int width = temp.width; - int height = temp.height; - - int n; - - int first = 1; - for(n=0; n<2*width; n++) { - int i = n < width ? n : 2*width - 1 - n; - double x = fmod(a + i * (b-a) / width, p->period); - if(x < 0) x += p->period; - int j = floor(x); - double y; - - if(p->waveform[j] <= 0) y = 0; - else y = p->waveform[j] * 0.4 / p->waveform_max; - - int k = round(y*height); - if(n < width) k = -k; - - if(first) { - cairo_move_to(c,i+.5,height/2+k+.5); - first = 0; - } else - cairo_line_to(c,i+.5,height/2+k+.5); - } + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + int n; + + int first = 1; + for(n=0; n<2*width; n++) { + int i = n < width ? n : 2*width - 1 - n; + double x = fmod(a + i * (b-a) / width, p->period); + if(x < 0) x += p->period; + int j = floor(x); + double y; + + if(p->waveform[j] <= 0) y = 0; + else y = p->waveform[j] * 0.4 / p->waveform_max; + + int k = round(y*height); + if(n < width) k = -k; + + if(first) { + cairo_move_to(c,i+.5,height/2+k+.5); + first = 0; + } else + cairo_line_to(c,i+.5,height/2+k+.5); + } } #ifdef DEBUG void draw_debug_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) { - int width = da->allocation.width; - int height = da->allocation.height; - - int i; - float max = 0; - - int ai = round(a); - int bi = 1+round(b); - if(ai < 0) ai = 0; - if(bi > p->sample_count) bi = p->sample_count; - for(i=ai; idebug[i] > max) - max = p->debug[i]; - - int first = 1; - for(i=0; i= p->sample_count) j = p->sample_count-1; - - int k = round((0.1+p->debug[j]/max)*0.8*height); - - if(first) { - cairo_move_to(c,i+.5,height-k-.5); - first = 0; - } else - cairo_line_to(c,i+.5,height-k-.5); - } - } + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + int i; + float max = 0; + + int ai = round(a); + int bi = 1+round(b); + if(ai < 0) ai = 0; + if(bi > p->sample_count) bi = p->sample_count; + for(i=ai; idebug[i] > max) + max = p->debug[i]; + + int first = 1; + for(i=0; i= p->sample_count) j = p->sample_count-1; + + int k = round((0.1+p->debug[j]/max)*0.8*height); + + if(first) { + cairo_move_to(c,i+.5,height-k-.5); + first = 0; + } else + cairo_line_to(c,i+.5,height-k-.5); + } + } } #endif double amplitude_to_time(double lift_angle, double amp) { - return asin(lift_angle / (2 * amp)) / M_PI; + return asin(lift_angle / (2 * amp)) / M_PI; } +/* Draw the watch icon at the start of the info area */ double draw_watch_icon(cairo_t *c, int signal) { - int happy = !!signal; - cairo_set_line_width(c,3); - cairo_set_source(c,happy?green:red); - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.35, OUTPUT_WINDOW_HEIGHT * (0.65 - 0.3*happy)); - cairo_stroke(c); - cairo_arc(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.4, 0, 2*M_PI); - cairo_stroke(c); - const int l = OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1); - int i; - cairo_set_line_width(c,1); - for(i = 0; i < signal; i++) { - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_stroke_preserve(c); - cairo_fill(c); - } - return OUTPUT_WINDOW_HEIGHT + 3*l; + int happy = !!signal; + cairo_set_line_width(c,3); + cairo_set_source(c,happy?green:red); + cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); + cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.35, OUTPUT_WINDOW_HEIGHT * (0.65 - 0.3*happy)); + cairo_stroke(c); + cairo_arc(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.4, 0, 2*M_PI); + cairo_stroke(c); + const int l = OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1); + int i; + cairo_set_line_width(c,1); + for(i = 0; i < signal; i++) { + cairo_move_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); + cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_stroke_preserve(c); + cairo_fill(c); + } + return OUTPUT_WINDOW_HEIGHT + 3*l; } double get_rate(int bph, double sample_rate, struct processing_buffers *p) { - return (7200/(bph*p->period / sample_rate) - 1)*24*3600; + return (7200/(bph*p->period / sample_rate) - 1)*24*3600; } double get_amplitude(double la, struct processing_buffers *p) { - double ret = -1; - if(p->tic_pulse > 0 && p->toc_pulse > 0) { - double tic_amp = la * .5 / sin(M_PI * p->tic_pulse / p->period); - double toc_amp = la * .5 / sin(M_PI * p->toc_pulse / p->period); - if(la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) - ret = (tic_amp + toc_amp) / 2; - } - return ret; + double ret = -1; + if(p->tic_pulse > 0 && p->toc_pulse > 0) { + double tic_amp = la * .5 / sin(M_PI * p->tic_pulse / p->period); + double toc_amp = la * .5 / sin(M_PI * p->toc_pulse / p->period); + if(la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) + ret = (tic_amp + toc_amp) / 2; + } + return ret; } -cairo_t *cairo_init(GtkWidget *widget) +/* Set up default line width and background color before each drawing operation */ +void cairo_init(cairo_t *c) { - cairo_t *c = gdk_cairo_create(gtk_widget_get_window(widget)); - cairo_set_line_width(c,1); - - cairo_set_source(c,black); - cairo_paint(c); - - return c; + cairo_set_line_width(c, 1); + + cairo_set_source(c,black); + cairo_paint(c); } double print_number(cairo_t *c, double x, double y, char *s) { - cairo_text_extents_t extents; - cairo_text_extents(c,"0",&extents); - double z = extents.x_advance; - char t[2]; - t[1] = 0; - while((t[0] = *s++)) { - cairo_text_extents(c,t,&extents); - cairo_move_to(c, x + (z - extents.x_advance) / 2, y); - cairo_show_text(c,t); - x += z; - } - return x; -} - -gboolean output_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) -{ - cairo_t *c = cairo_init(widget); - - int old; - struct processing_buffers *p = get_data(w,&old); - - double x = draw_watch_icon(c,w->signal); - - char outputs[8][20]; - - if(p) { - int bph = w->guessed_bph; - int rate = round(get_rate(bph, w->sample_rate, p)); - double be = fabs(p->be) * 1000 / p->sample_rate; - double amp = get_amplitude(w->la, p); - char rates[20]; - sprintf(rates,"%s%d",rate > 0 ? "+" : rate < 0 ? "-" : "",abs(rate)); - sprintf(outputs[0],"%4s",rates); - sprintf(outputs[2]," %4.1f",be); - if(amp > 0) - sprintf(outputs[4]," %3.0f",amp); - else - strcpy(outputs[4]," ---"); - } else { - strcpy(outputs[0],"----"); - strcpy(outputs[2]," ----"); - strcpy(outputs[4]," ---"); - } - sprintf(outputs[6]," %d",w->guessed_bph); - - strcpy(outputs[1]," s/d"); - strcpy(outputs[3]," ms"); - strcpy(outputs[5]," deg"); - strcpy(outputs[7]," bph"); - - cairo_text_extents_t extents; - - cairo_set_font_size(c, OUTPUT_FONT); - cairo_text_extents(c,"0",&extents); - double y = (double)OUTPUT_WINDOW_HEIGHT/2 - extents.y_bearing - extents.height/2; - - int i; - for(i=0; i<8; i++) { - if(i%2) { - cairo_set_source(c, white); - cairo_move_to(c,x,y); - cairo_set_font_size(c, OUTPUT_FONT*2/3); - cairo_show_text(c,outputs[i]); - cairo_text_extents(c,outputs[i],&extents); - x += extents.x_advance; - } else { - cairo_set_source(c, i > 4 || !p || !old ? white : yellow); - cairo_set_font_size(c, OUTPUT_FONT); - x = print_number(c,x,y,outputs[i]); - } - } -#ifdef DEBUG - { - static GTimer *timer = NULL; - if (!timer) timer = g_timer_new(); - else { - char s[100]; - sprintf(s," %.2f fps",1./g_timer_elapsed(timer, NULL)); - cairo_set_source(c, white); - cairo_set_font_size(c, OUTPUT_FONT); - cairo_move_to(c,x,y); - cairo_show_text(c,s); - g_timer_reset(timer); - } - } -#endif - - cairo_destroy(c); - - return FALSE; + cairo_text_extents_t extents; + cairo_text_extents(c,"0",&extents); + double z = extents.x_advance; + char t[2]; + t[1] = 0; + while((t[0] = *s++)) { + cairo_text_extents(c,t,&extents); + cairo_move_to(c, x + (z - extents.x_advance) / 2, y); + cairo_show_text(c,t); + x += z; + } + return x; } -void expose_waveform( - struct main_window *w, - GtkWidget *da, - int (*get_offset)(struct processing_buffers*), - double (*get_pulse)(struct processing_buffers*)) +/* Draws either the tic or toc waveform & grid in their respective widget */ +void draw_waveform( + struct main_window *w, + GtkWidget *da, + cairo_t *cr, + int (*get_offset)(struct processing_buffers*), + double (*get_pulse)(struct processing_buffers*) ) { - cairo_t *c = cairo_init(da); - - GtkAllocation temp; - gtk_widget_get_allocation (da, &temp); - - int width = temp.width; - int height = temp.height; - - gtk_widget_get_allocation (w->window, &temp); - int font = temp.width / 90; - if(font < 12) - font = 12; - int i; - - cairo_set_font_size(c,font); - - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { - int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); - cairo_move_to(c, x + .5, height / 2 + .5); - cairo_line_to(c, x + .5, height - .5); - if(i%5) - cairo_set_source(c,green); - else - cairo_set_source(c,red); - cairo_stroke(c); - } - cairo_set_source(c,white); - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { - if(!(i%5)) { - int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); - char s[10]; - sprintf(s,"%d",i); - cairo_move_to(c,x+font/4,height-font/2); - cairo_show_text(c,s); - } - } - - cairo_text_extents_t extents; - - cairo_text_extents(c,"ms",&extents); - cairo_move_to(c,width - extents.x_advance - font/4,height-font/2); - cairo_show_text(c,"ms"); - - int old; - struct processing_buffers *p = get_data(w,&old); - double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; - - for(i = 10; i < 360; i+=10) { - if(2*i < w->la) continue; - double t = period*amplitude_to_time(w->la,i); - if(t > .001 * NEGATIVE_SPAN) continue; - int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); - cairo_move_to(c, x+.5, .5); - cairo_line_to(c, x+.5, height / 2 + .5); - if(i % 50) - cairo_set_source(c,green); - else - cairo_set_source(c,red); - cairo_stroke(c); - } - - double last_x = 0; - cairo_set_source(c,white); - for(i = 50; i < 360; i+=50) { - double t = period*amplitude_to_time(w->la,i); - if(t > .001 * NEGATIVE_SPAN) continue; - int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); - if(x > last_x) { - char s[10]; - - sprintf(s,"%d",abs(i)); - cairo_move_to(c, x + font/4, font * 3 / 2); - cairo_show_text(c,s); - cairo_text_extents(c,s,&extents); - last_x = x + font/4 + extents.x_advance; - } - } - - cairo_text_extents(c,"deg",&extents); - cairo_move_to(c,width - extents.x_advance - font/4,font * 3 / 2); - cairo_show_text(c,"deg"); - - if(p) { - double span = 0.001 * w->sample_rate; - int offset = get_offset(p); - - double a = offset - span * NEGATIVE_SPAN; - double b = offset + span * POSITIVE_SPAN; - - draw_graph(a,b,c,p,da); - - cairo_set_source(c,old?yellow:white); - cairo_stroke_preserve(c); - cairo_fill(c); - - double pulse = get_pulse(p); - if(pulse > 0) { - int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); - cairo_move_to(c, x, 1); - cairo_line_to(c, x, height - 1); - cairo_set_source(c,blue); - cairo_set_line_width(c,2); - cairo_stroke(c); - } - } else { - cairo_move_to(c, .5, height / 2 + .5); - cairo_line_to(c, width - .5, height / 2 + .5); - cairo_set_source(c,yellow); - cairo_stroke(c); - } - - cairo_destroy(c); + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + int fontsize = gtk_widget_get_allocated_width(w->window) / 90; + if(fontsize < 12) + fontsize = 12; + int i; + + cairo_set_font_size(cr, fontsize); + + for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); + cairo_move_to(cr, x + .5, height / 2 + .5); + cairo_line_to(cr, x + .5, height - .5); + if(i%5) + cairo_set_source(cr, green); + else + cairo_set_source(cr, red); + cairo_stroke(cr); + } + cairo_set_source(cr, white); + for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + if(!(i%5)) { + int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); + char s[10]; + sprintf(s,"%d",i); + cairo_move_to(cr,x+fontsize/4, height-fontsize/2); + cairo_show_text(cr,s); + } + } + + cairo_text_extents_t extents; + + cairo_text_extents(cr,"ms", &extents); + cairo_move_to(cr,width - extents.x_advance - fontsize/4, height-fontsize/2); + cairo_show_text(cr, "ms"); + + int old; + struct processing_buffers *p = get_data(w, &old); + double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; + + for(i = 10; i < 360; i+=10) { + if(2*i < w->la) continue; + double t = period*amplitude_to_time(w->la, i); + if(t > .001 * NEGATIVE_SPAN) continue; + int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); + cairo_move_to(cr, x+.5, .5); + cairo_line_to(cr, x+.5, height / 2 + .5); + if(i % 50) + cairo_set_source(cr,green); + else + cairo_set_source(cr,red); + cairo_stroke(cr); + } + + double last_x = 0; + cairo_set_source(cr, white); + for(i = 50; i < 360; i+=50) { + double t = period*amplitude_to_time(w->la, i); + if(t > .001 * NEGATIVE_SPAN) continue; + int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); + if(x > last_x) { + char s[10]; + + sprintf(s,"%d",abs(i)); + cairo_move_to(cr, x + fontsize/4, fontsize * 3 / 2); + cairo_show_text(cr, s); + cairo_text_extents(cr, s, &extents); + last_x = x + fontsize/4 + extents.x_advance; + } + } + + cairo_text_extents(cr, "deg", &extents); + cairo_move_to(cr,width - extents.x_advance - fontsize/4, fontsize * 3 / 2); + cairo_show_text(cr,"deg"); + + if(p) { + double span = 0.001 * w->sample_rate; + int offset = get_offset(p); + + double a = offset - span * NEGATIVE_SPAN; + double b = offset + span * POSITIVE_SPAN; + + draw_graph(a,b,cr,p,da); + + cairo_set_source(cr, old?yellow:white); + cairo_stroke_preserve(cr); + cairo_fill(cr); + + double pulse = get_pulse(p); + if(pulse > 0) { + int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); + cairo_move_to(cr, x, 1); + cairo_line_to(cr, x, height - 1); + cairo_set_source(cr, blue); + cairo_set_line_width(cr, 2); + cairo_stroke(cr); + } + } else { + cairo_move_to(cr, .5, height / 2 + .5); + cairo_line_to(cr, width - .5, height / 2 + .5); + cairo_set_source(cr, yellow); + cairo_stroke(cr); + } + } int get_tic(struct processing_buffers *p) { - return p->tic; + return p->tic; } int get_toc(struct processing_buffers *p) { - return p->toc; + return p->toc; } double get_tic_pulse(struct processing_buffers *p) { - return p->tic_pulse; + return p->tic_pulse; } double get_toc_pulse(struct processing_buffers *p) { - return p->toc_pulse; + return p->toc_pulse; } -gboolean tic_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - expose_waveform(w, w->tic_drawing_area, get_tic, get_tic_pulse); - return FALSE; + cairo_init(cr); + + int old; + struct processing_buffers *p = get_data(w, &old); + + double x = draw_watch_icon(cr, w->signal); + + char outputs[8][20]; + + if(p) { + int bph = w->guessed_bph; + int rate = round(get_rate(bph, w->sample_rate, p)); + double be = fabs(p->be) * 1000 / p->sample_rate; + double amp = get_amplitude(w->la, p); + char rates[20]; + sprintf(rates,"%s%d",rate > 0 ? "+" : rate < 0 ? "-" : "",abs(rate)); + sprintf(outputs[0],"%4s",rates); + sprintf(outputs[2]," %4.1f",be); + if(amp > 0) + sprintf(outputs[4]," %3.0f",amp); + else + strcpy(outputs[4]," ---"); + } else { + strcpy(outputs[0],"----"); + strcpy(outputs[2]," ----"); + strcpy(outputs[4]," ---"); + } + sprintf(outputs[6]," %d",w->guessed_bph); + + strcpy(outputs[1]," s/d"); + strcpy(outputs[3]," ms"); + strcpy(outputs[5]," deg"); + strcpy(outputs[7]," bph"); + + cairo_text_extents_t extents; + + cairo_set_font_size(cr, OUTPUT_FONT); + cairo_text_extents(cr,"0", &extents); + double y = (double)OUTPUT_WINDOW_HEIGHT/2 - extents.y_bearing - extents.height/2; + + int i; + for(i=0; i <8; i++) { + if(i%2) { + cairo_set_source(cr, white); + cairo_move_to(cr,x,y); + cairo_set_font_size(cr, OUTPUT_FONT*2/3); + cairo_show_text(cr,outputs[i]); + cairo_text_extents(cr,outputs[i],&extents); + x += extents.x_advance; + } else { + cairo_set_source(cr, i > 4 || !p || !old ? white : yellow); + cairo_set_font_size(cr, OUTPUT_FONT); + x = print_number(cr,x,y,outputs[i]); + } + } +#ifdef DEBUG + { + static GTimer *timer = NULL; + if (!timer) timer = g_timer_new(); + else { + char s[100]; + sprintf(s," %.2f fps",1./g_timer_elapsed(timer, NULL)); + cairo_set_source(c, white); + cairo_set_font_size(c, OUTPUT_FONT); + cairo_move_to(c,x,y); + cairo_show_text(c,s); + g_timer_reset(timer); + } + } +#endif + + return FALSE; } -gboolean toc_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +gboolean tic_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - expose_waveform(w, w->toc_drawing_area, get_toc, get_toc_pulse); - return FALSE; + draw_waveform(w, w->tic_drawing_area, cr, get_tic, get_tic_pulse); + return FALSE; } -gboolean period_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +gboolean toc_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - cairo_t *c = cairo_init(widget); - - GtkAllocation temp; - gtk_widget_get_allocation (w->period_drawing_area, &temp); - - int width = temp.width; - int height = temp.height; - - int old; - struct processing_buffers *p = get_data(w,&old); - - double toc,a=0,b=0; - - if(p) { - toc = p->tic < p->toc ? p->toc : p->toc + p->period; - a = ((double)p->tic + toc)/2 - p->period/2; - b = ((double)p->tic + toc)/2 + p->period/2; - - cairo_move_to(c, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_line_to(c, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(c, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(c, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(c,blueish); - cairo_fill(c); - - cairo_move_to(c, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_line_to(c, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(c, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(c, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(c,blueish); - cairo_fill(c); - } - - int i; - for(i = 1; i < 16; i++) { - int x = i * width / 16; - cairo_move_to(c, x+.5, .5); - cairo_line_to(c, x+.5, height - .5); - if(i % 4) - cairo_set_source(c,green); - else - cairo_set_source(c,red); - cairo_stroke(c); - } - - if(p) { - draw_graph(a,b,c,p,w->period_drawing_area); - - cairo_set_source(c,old?yellow:white); - cairo_stroke_preserve(c); - cairo_fill(c); - } else { - cairo_move_to(c, .5, height / 2 + .5); - cairo_line_to(c, width - .5, height / 2 + .5); - cairo_set_source(c,yellow); - cairo_stroke(c); - } - - cairo_destroy(c); + draw_waveform(w, w->toc_drawing_area, cr, get_toc, get_toc_pulse); + return FALSE; +} - return FALSE; +gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) +{ + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(w->period_drawing_area); + int height = gtk_widget_get_allocated_height(w->period_drawing_area); + + int old; + struct processing_buffers *p = get_data(w, &old); + + double toc, a=0, b=0; + + if(p) { + toc = p->tic < p->toc ? p->toc : p->toc + p->period; + a = ((double)p->tic + toc)/2 - p->period/2; + b = ((double)p->tic + toc)/2 + p->period/2; + + cairo_move_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_line_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_set_source(cr, blueish); + cairo_fill(cr); + + cairo_move_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_line_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_set_source(cr, blueish); + cairo_fill(cr); + } + + int i; + for(i = 1; i < 16; i++) { + int x = i * width / 16; + cairo_move_to(cr, x+.5, .5); + cairo_line_to(cr, x+.5, height - .5); + if(i % 4) + cairo_set_source(cr, green); + else + cairo_set_source(cr, red); + cairo_stroke(cr); + } + + if(p) { + draw_graph(a,b,cr,p,w->period_drawing_area); + + cairo_set_source(cr, old?yellow:white); + cairo_stroke_preserve(cr); + cairo_fill(cr); + } else { + cairo_move_to(cr, .5, height / 2 + .5); + cairo_line_to(cr, width - .5, height / 2 + .5); + cairo_set_source(cr, yellow); + cairo_stroke(cr); + } + + return FALSE; } extern volatile uint64_t timestamp; -gboolean paperstrip_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - int i,old; - struct processing_buffers *p = get_data(w,&old); + int i,old; + struct processing_buffers *p = get_data(w, &old); #ifdef LIGHT - uint64_t time = timestamp / 2; + uint64_t time = timestamp / 2; #else - uint64_t time = timestamp; + uint64_t time = timestamp; #endif - if(p && !old) { - uint64_t last = w->events[w->events_wp]; - for(i=0; ievents[i]; i++) - if(p->events[i] > last + floor(p->period / 4)) { - if(++w->events_wp == EVENTS_COUNT) w->events_wp = 0; - w->events[w->events_wp] = p->events[i]; - debug("event at %llu\n",w->events[w->events_wp]); - } - w->events_from = p->timestamp - ceil(p->period); - } else { - w->events_from = time; - } - - cairo_t *c = cairo_init(widget); - - GtkAllocation temp; - gtk_widget_get_allocation (w->paperstrip_drawing_area, &temp); - - int width = temp.width; - int height = temp.height; - - int stopped = 0; - if(w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { - time = 5 * w->sample_rate + w->events[w->events_wp]; - stopped = 1; - } - - int strip_width = round(width / (1 + PAPERSTRIP_MARGIN)); - - cairo_set_line_width(c,1.3); - - if(p && w->events[w->events_wp]) { - double rate = get_rate(w->guessed_bph, w->sample_rate, p); - double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); - if(slope <= 1 && slope >= -1) { - for(i=0; i<4; i++) { - double y = 0; - cairo_move_to(c, (double)width * (i+.5) / 4, 0); - for(;;) { - double x = y * slope + (double)width * (i+.5) / 4; - x = fmod(x, width); - if(x < 0) x += width; - double nx = x + slope * (height - y); - if(nx >= 0 && nx <= width) { - cairo_line_to(c, nx, height); - break; - } else { - double d = slope > 0 ? width - x : x; - y += d / fabs(slope); - cairo_line_to(c, slope > 0 ? width : 0, y); - y += 1; - if(y > height) break; - cairo_move_to(c, slope > 0 ? 0 : width, y); - } - } - } - cairo_set_source(c, blue); - cairo_stroke(c); - } - } - - cairo_set_line_width(c,1); - - int left_margin = (width - strip_width) / 2; - int right_margin = (width + strip_width) / 2; - cairo_move_to(c, left_margin + .5, .5); - cairo_line_to(c, left_margin + .5, height - .5); - cairo_move_to(c, right_margin + .5, .5); - cairo_line_to(c, right_margin + .5, height - .5); - cairo_set_source(c, green); - cairo_stroke(c); - - double sweep = w->sample_rate * 3600. / w->guessed_bph; - double now = sweep*ceil(time/sweep); - double ten_s = w->sample_rate * 10 / sweep; - double last_line = fmod(now/sweep, ten_s); - int last_tenth = floor(now/(sweep*ten_s)); - for(i=0;;i++) { - double y = 0.5 + round(last_line + i*ten_s); - if(y > height) break; - cairo_move_to(c, .5, y); - cairo_line_to(c, width-.5, y); - cairo_set_source(c, (last_tenth-i)%6 ? green : red); - cairo_stroke(c); - } - - cairo_set_source(c,stopped?yellow:white); - for(i = w->events_wp;;) { - if(!w->events[i]) break; - double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); - int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); - int row = floor(event / sweep); - if(row >= height) break; - cairo_move_to(c,column,row); - cairo_line_to(c,column+1,row); - cairo_line_to(c,column+1,row+1); - cairo_line_to(c,column,row+1); - cairo_line_to(c,column,row); - cairo_fill(c); - if(column < width - strip_width && row > 0) { - column += strip_width; - row -= 1; - cairo_move_to(c,column,row); - cairo_line_to(c,column+1,row); - cairo_line_to(c,column+1,row+1); - cairo_line_to(c,column,row+1); - cairo_line_to(c,column,row); - cairo_fill(c); - } - if(--i < 0) i = EVENTS_COUNT - 1; - if(i == w->events_wp) break; - } - - cairo_set_source(c,white); - cairo_set_line_width(c,2); - cairo_move_to(c, left_margin + 3, height - 20.5); - cairo_line_to(c, right_margin - 3, height - 20.5); - cairo_stroke(c); - cairo_set_line_width(c,1); - cairo_move_to(c, left_margin + .5, height - 20.5); - cairo_line_to(c, left_margin + 5.5, height - 15.5); - cairo_line_to(c, left_margin + 5.5, height - 25.5); - cairo_line_to(c, left_margin + .5, height - 20.5); - cairo_fill(c); - cairo_move_to(c, right_margin + .5, height - 20.5); - cairo_line_to(c, right_margin - 4.5, height - 15.5); - cairo_line_to(c, right_margin - 4.5, height - 25.5); - cairo_line_to(c, right_margin + .5, height - 20.5); - cairo_fill(c); - - char s[100]; - cairo_text_extents_t extents; - - gtk_widget_get_allocation (w->window, &temp); - int font = temp.width / 90; - if(font < 12) - font = 12; - cairo_set_font_size(c,font); - - sprintf(s, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); - cairo_text_extents(c,s,&extents); - cairo_move_to(c, (width - extents.x_advance)/2, height - 30); - cairo_show_text(c,s); - - cairo_destroy(c); - - return FALSE; + if(p && !old) { + uint64_t last = w->events[w->events_wp]; + for(i=0; ievents[i]; i++) + if(p->events[i] > last + floor(p->period / 4)) { + if(++w->events_wp == EVENTS_COUNT) w->events_wp = 0; + w->events[w->events_wp] = p->events[i]; + debug("event at %llu\n", w->events[w->events_wp]); + } + w->events_from = p->timestamp - ceil(p->period); + } else { + w->events_from = time; + } + + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(w->paperstrip_drawing_area); + int height = gtk_widget_get_allocated_height(w->paperstrip_drawing_area); + + int stopped = 0; + if(w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { + time = 5 * w->sample_rate + w->events[w->events_wp]; + stopped = 1; + } + + int strip_width = round(width / (1 + PAPERSTRIP_MARGIN)); + + cairo_set_line_width(cr, 1.3); + + if(p && w->events[w->events_wp]) { + double rate = get_rate(w->guessed_bph, w->sample_rate, p); + double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); + if(slope <= 1 && slope >= -1) { + for(i=0; i<4; i++) { + double y = 0; + cairo_move_to(cr, (double)width * (i+.5) / 4, 0); + for(;;) { + double x = y * slope + (double)width * (i+.5) / 4; + x = fmod(x, width); + if(x < 0) x += width; + double nx = x + slope * (height - y); + if(nx >= 0 && nx <= width) { + cairo_line_to(cr, nx, height); + break; + } else { + double d = slope > 0 ? width - x : x; + y += d / fabs(slope); + cairo_line_to(cr, slope > 0 ? width : 0, y); + y += 1; + if(y > height) break; + cairo_move_to(cr, slope > 0 ? 0 : width, y); + } + } + } + cairo_set_source(cr, blue); + cairo_stroke(cr); + } + } + + cairo_set_line_width(cr,1); + + int left_margin = (width - strip_width) / 2; + int right_margin = (width + strip_width) / 2; + cairo_move_to(cr, left_margin + .5, .5); + cairo_line_to(cr, left_margin + .5, height - .5); + cairo_move_to(cr, right_margin + .5, .5); + cairo_line_to(cr, right_margin + .5, height - .5); + cairo_set_source(cr, green); + cairo_stroke(cr); + + double sweep = w->sample_rate * 3600. / w->guessed_bph; + double now = sweep*ceil(time/sweep); + double ten_s = w->sample_rate * 10 / sweep; + double last_line = fmod(now/sweep, ten_s); + int last_tenth = floor(now/(sweep*ten_s)); + for(i=0;;i++) { + double y = 0.5 + round(last_line + i*ten_s); + if(y > height) break; + cairo_move_to(cr, .5, y); + cairo_line_to(cr, width-.5, y); + cairo_set_source(cr, (last_tenth-i)%6 ? green : red); + cairo_stroke(cr); + } + + cairo_set_source(cr, stopped?yellow:white); + for(i = w->events_wp;;) { + if(!w->events[i]) break; + double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); + int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); + int row = floor(event / sweep); + if(row >= height) break; + cairo_move_to(cr,column,row); + cairo_line_to(cr,column+1,row); + cairo_line_to(cr,column+1,row+1); + cairo_line_to(cr,column,row+1); + cairo_line_to(cr,column,row); + cairo_fill(cr); + if(column < width - strip_width && row > 0) { + column += strip_width; + row -= 1; + cairo_move_to(cr,column,row); + cairo_line_to(cr,column+1,row); + cairo_line_to(cr,column+1,row+1); + cairo_line_to(cr,column,row+1); + cairo_line_to(cr,column,row); + cairo_fill(cr); + } + if(--i < 0) i = EVENTS_COUNT - 1; + if(i == w->events_wp) break; + } + + // Draw the arrowed line for the ms scale at the bottom + cairo_set_source(cr, white); + cairo_set_line_width(cr, 2); + cairo_move_to(cr, left_margin + 3, height - 20.5); + cairo_line_to(cr, right_margin - 3, height - 20.5); + cairo_stroke(cr); + cairo_set_line_width(cr,1); + cairo_move_to(cr, left_margin + .5, height - 20.5); + cairo_line_to(cr, left_margin + 5.5, height - 15.5); + cairo_line_to(cr, left_margin + 5.5, height - 25.5); + cairo_line_to(cr, left_margin + .5, height - 20.5); + cairo_fill(cr); + cairo_move_to(cr, right_margin + .5, height - 20.5); + cairo_line_to(cr, right_margin - 4.5, height - 15.5); + cairo_line_to(cr, right_margin - 4.5, height - 25.5); + cairo_line_to(cr, right_margin + .5, height - 20.5); + cairo_fill(cr); + + // Write the ms scale at the bottom of the paperstrip + + int fontsize = gtk_widget_get_allocated_width(w->window) / 90; + if(fontsize < 12) + fontsize = 12; + cairo_set_font_size(cr, fontsize); + + char s[100]; + cairo_text_extents_t extents; + + sprintf(s, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); + cairo_text_extents(cr,s,&extents); + cairo_move_to(cr, (width - extents.x_advance)/2, height - 30); + cairo_show_text(cr, s); + + return FALSE; } #ifdef DEBUG -gboolean debug_expose_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +gboolean debug_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - cairo_t *c = cairo_init(widget); - - int old = 0; - struct processing_buffers *p = get_data(w,&old); - - if(p) { - double a = p->period / 10; - double b = p->period * 2; - - draw_debug_graph(a,b,c,p,w->debug_drawing_area); - - cairo_set_source(c,old?yellow:white); - cairo_stroke(c); - } - - cairo_destroy(c); - - return FALSE; + int old = 0; + struct processing_buffers *p = get_data(w, &old); + + if(p) { + double a = p->period / 10; + double b = p->period * 2; + + cairo_init(cr); + + draw_debug_graph(a,b,cr,p,w->debug_drawing_area); + + cairo_set_source(cr, old?yellow:white); + cairo_stroke(cr); + } + + return FALSE; } #endif +/* Called when the user changes the bph box value */ void handle_bph_change(GtkComboBox *b, struct main_window *w) { - char *s = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(b)); - if(s) { - int n; - char *t; - n = (int)strtol(s,&t,10); - if(*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; - else w->bph = w->guessed_bph = n; - g_free(s); - recompute(w); - redraw(w); - } + char *s = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(b)); + if(s) { + int n; + char *t; + n = (int)strtol(s,&t,10); + if(*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; + else w->bph = w->guessed_bph = n; + g_free(s); + recompute(w); + redraw(w); + } } +/* Called when the user changes the lift angle value */ void handle_la_change(GtkSpinButton *b, struct main_window *w) { - double la = gtk_spin_button_get_value(b); - if(la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; - w->la = la; - redraw(w); + double la = gtk_spin_button_get_value(b); + if(la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; + w->la = la; + redraw(w); } +/* Called when the user clicks the Clear button */ void handle_clear_trace(GtkButton *b, struct main_window *w) { - memset(w->events,0,EVENTS_COUNT*sizeof(uint64_t)); - redraw(w); + memset(w->events, 0, EVENTS_COUNT*sizeof(uint64_t)); + redraw(w); } +/* Called when the user clicks the Center button */ void handle_center_trace(GtkButton *b, struct main_window *w) { - uint64_t last_ev = w->events[w->events_wp]; - if(last_ev) { - double sweep = w->sample_rate * 3600. / (PAPERSTRIP_ZOOM * w->guessed_bph); - w->trace_centering = fmod(last_ev + .5*sweep , sweep); - } else - w->trace_centering = 0; - redraw(w); + uint64_t last_ev = w->events[w->events_wp]; + if(last_ev) { + double sweep = w->sample_rate * 3600. / (PAPERSTRIP_ZOOM * w->guessed_bph); + w->trace_centering = fmod(last_ev + .5*sweep , sweep); + } else + w->trace_centering = 0; + redraw(w); } void quit() { - gtk_main_quit(); + gtk_main_quit(); } /* Set up the main window and populate with widgets */ void init_main_window(struct main_window *w) { - w->signal = 0; - - w->events = malloc(EVENTS_COUNT * sizeof(uint64_t)); - memset(w->events,0,EVENTS_COUNT * sizeof(uint64_t)); - w->events_wp = 0; - w->events_from = 0; - w->trace_centering = 0; - - w->guessed_bph = w->last_bph = DEFAULT_BPH; - w->bph = 0; - w->la = DEFAULT_LA; - - w->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - gtk_container_set_border_width(GTK_CONTAINER(w->window), 10); // Border around the window - g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); - g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); - - gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); - - GtkWidget *vbox = gtk_vbox_new(FALSE, 10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_container_add(GTK_CONTAINER(w->window), vbox); - gtk_widget_show(vbox); - - GtkWidget *hbox = gtk_hbox_new(FALSE, 10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); - gtk_widget_show(hbox); - - // BPH label - GtkWidget *label = gtk_label_new("bph"); - // gtk_widget_set_has_window (label, FALSE); // GTK_WIDGET_SET_FLAGS(label,GTK_NO_WINDOW); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_widget_show(label); - - // BPH combo box - w->bph_combo_box = gtk_combo_box_text_new_with_entry(); - gtk_box_pack_start(GTK_BOX(hbox), w->bph_combo_box, FALSE, TRUE, 0); - // Fill in pre-defined values - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "guess"); - int *bph; - for(bph = preset_bph; *bph; bph++) { - char s[100]; - sprintf(s,"%d", *bph); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); - } - gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); - g_signal_connect (w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); - gtk_widget_show(w->bph_combo_box); - - // Lift angle label - label = gtk_label_new("lift angle"); - // gtk_widget_set_has_window (label, FALSE); // GTK_WIDGET_SET_FLAGS(label,GTK_NO_WINDOW); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_widget_show(label); - - // Lift angle spin button - w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); - gtk_box_pack_start(GTK_BOX(hbox), w->la_spin_button, FALSE, TRUE, 0); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); // Start at default value - g_signal_connect (w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); - gtk_widget_show(w->la_spin_button); - - // Info area on top - w->output_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->output_drawing_area, 700, OUTPUT_WINDOW_HEIGHT); - gtk_box_pack_start(GTK_BOX(vbox),w->output_drawing_area, FALSE, TRUE, 0); - g_signal_connect (w->output_drawing_area, "expose_event", G_CALLBACK(output_expose_event), w); - gtk_widget_set_events(w->output_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->output_drawing_area); - - GtkWidget *hbox2 = gtk_hbox_new(FALSE, 10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_box_pack_start(GTK_BOX(vbox), hbox2, TRUE, TRUE, 0); - gtk_widget_show(hbox2); - - GtkWidget *vbox2 = gtk_vbox_new(FALSE, 10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_box_pack_start(GTK_BOX(hbox2), vbox2, FALSE, TRUE, 0); - gtk_widget_show(vbox2); - - // Paperstrip - w->paperstrip_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->paperstrip_drawing_area, 200, 400); - gtk_box_pack_start(GTK_BOX(vbox2), w->paperstrip_drawing_area, TRUE, TRUE, 0); - g_signal_connect (w->paperstrip_drawing_area, "expose_event", G_CALLBACK(paperstrip_expose_event), w); - gtk_widget_set_events(w->paperstrip_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->paperstrip_drawing_area); - - GtkWidget *hbox3 = gtk_hbox_new(FALSE, 10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_box_pack_start(GTK_BOX(vbox2), hbox3, FALSE, TRUE, 0); - gtk_widget_show(hbox3); - - // CLEAR button - GtkWidget *clear_button = gtk_button_new_with_label("Clear"); - gtk_box_pack_start(GTK_BOX(hbox3), clear_button, TRUE, TRUE, 0); - g_signal_connect (clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); - gtk_widget_show(clear_button); - - // CENTER button - GtkWidget *center_button = gtk_button_new_with_label("Center"); - gtk_box_pack_start(GTK_BOX(hbox3), center_button, TRUE, TRUE, 0); - g_signal_connect (center_button, "clicked", G_CALLBACK(handle_center_trace), w); - gtk_widget_show(center_button); - - GtkWidget *vbox3 = gtk_vbox_new(FALSE,10); // Replaced by GtkGrid in GTK+ 3.2 - gtk_box_pack_start(GTK_BOX(hbox2), vbox3, TRUE, TRUE, 0); - gtk_widget_show(vbox3); - - // Tic waveform area - w->tic_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->tic_drawing_area, 700, 100); - gtk_box_pack_start(GTK_BOX(vbox3), w->tic_drawing_area, TRUE, TRUE, 0); - g_signal_connect (w->tic_drawing_area, "expose_event", G_CALLBACK(tic_expose_event), w); - gtk_widget_set_events(w->tic_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->tic_drawing_area); - - // Toc waveform area - w->toc_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->toc_drawing_area, 700, 100); - gtk_box_pack_start(GTK_BOX(vbox3), w->toc_drawing_area, TRUE, TRUE, 0); - g_signal_connect (w->toc_drawing_area, "expose_event", G_CALLBACK(toc_expose_event), w); - gtk_widget_set_events(w->toc_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->toc_drawing_area); - - // Period waveform area - w->period_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->period_drawing_area, 700, 100); - gtk_box_pack_start(GTK_BOX(vbox3), w->period_drawing_area, TRUE, TRUE, 0); - g_signal_connect (w->period_drawing_area, "expose_event", G_CALLBACK(period_expose_event), w); - gtk_widget_set_events(w->period_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->period_drawing_area); - + w->signal = 0; + + w->events = malloc(EVENTS_COUNT * sizeof(uint64_t)); + memset(w->events,0,EVENTS_COUNT * sizeof(uint64_t)); + w->events_wp = 0; + w->events_from = 0; + w->trace_centering = 0; + + w->guessed_bph = w->last_bph = DEFAULT_BPH; + w->bph = 0; + w->la = DEFAULT_LA; + + gtk_container_set_border_width(GTK_CONTAINER(w->window), 5); // Border around the window + g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); + g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); + + gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); + + // Populate the settings grid + GtkWidget *settings_grid = gtk_grid_new(); // The grid containing the settings, default to horizontal orientation + gtk_grid_set_column_spacing(GTK_GRID(settings_grid), 2); + + // Beat mode Label + GtkWidget *bph_label = gtk_label_new("Beat mode"); + gtk_container_add (GTK_CONTAINER(settings_grid), bph_label); // Add to grid + + // BPH combo box + w->bph_combo_box = gtk_combo_box_text_new_with_entry(); + // Fill in pre-defined values + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "Auto"); + int *bph; + for(bph = preset_bph; *bph; bph++) { + char s[100]; + sprintf(s,"%d", *bph); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); + gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus + g_signal_connect (w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); + gtk_container_add (GTK_CONTAINER(settings_grid), w->bph_combo_box); + + // Lift angle label + GtkWidget *la_label = gtk_label_new("Lift angle"); + gtk_widget_set_margin_start(la_label, 10); // Make space from the widget in front + gtk_container_add (GTK_CONTAINER(settings_grid), la_label); + + // Lift angle spin button + w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); // Start at default value + g_signal_connect (w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); + gtk_container_add (GTK_CONTAINER(settings_grid), w->la_spin_button); + #ifdef DEBUG - w->debug_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->debug_drawing_area, 500, 100); - gtk_box_pack_start(GTK_BOX(vbox3), w->debug_drawing_area, TRUE, TRUE, 0); - g_signal_connect (w->debug_drawing_area, "expose_event", G_CALLBACK(debug_expose_event), w); - gtk_widget_set_events(w->debug_drawing_area, GDK_EXPOSURE_MASK); - gtk_widget_show(w->debug_drawing_area); + w->debug_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->debug_drawing_area, 500, 100); + g_signal_connect (w->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), w); + gtk_widget_set_events(w->debug_drawing_area, GDK_EXPOSURE_MASK); + + gtk_container_add (GTK_CONTAINER(settings_grid), w->debug_drawing_area); + printf("DEBUG!\n"); #endif - - gtk_window_maximize(GTK_WINDOW(w->window)); - gtk_widget_show(w->window); - gtk_window_set_focus(GTK_WINDOW(w->window), NULL); + + // Info area + w->info_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->info_drawing_area, 720, OUTPUT_WINDOW_HEIGHT); + g_signal_connect (w->info_drawing_area, "draw", G_CALLBACK(info_draw_event), w); + gtk_widget_set_events(w->info_drawing_area, GDK_EXPOSURE_MASK); + + // Populate the panes + GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_wide_handle(GTK_PANED(panes), TRUE); + + /* Clunky way to make the pane handle bigger, but seems to be the only option + otherwise the max is 5px with gtk_paned_set_wide_handle() */ + GtkStyleContext *sc = gtk_widget_get_style_context(panes); + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_data(provider, "* {\n" + " -GtkPaned-handle-size: 10;\n" + " }\n" + , -1, NULL); + + gtk_style_context_add_provider(sc, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (provider); + + + GtkWidget *left_grid = gtk_grid_new(); + GtkWidget *right_grid = gtk_grid_new(); + + // Set direction of the right grid since we're using gtk_container_add() to add contents + gtk_orientable_set_orientation (GTK_ORIENTABLE (right_grid), GTK_ORIENTATION_VERTICAL); + + gtk_grid_set_row_spacing(GTK_GRID(left_grid), 5); + gtk_grid_set_row_spacing(GTK_GRID(right_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(left_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(right_grid), 5); + + // The 3 waveforms on the right pane should all be equal height + gtk_grid_set_row_homogeneous(GTK_GRID(right_grid), TRUE); + + // Add the grids to the two panes + gtk_paned_pack1(GTK_PANED(panes), left_grid, TRUE, FALSE); + gtk_paned_pack2(GTK_PANED(panes), right_grid, TRUE, FALSE); + + gtk_widget_set_size_request(left_grid, 100, -1); // Minimum size of left pane + gtk_widget_set_size_request(right_grid, 200, 300); // Minimum size of right pane + + // Paperstrip + w->paperstrip_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) + g_signal_connect (w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); + gtk_widget_set_events(w->paperstrip_drawing_area, GDK_EXPOSURE_MASK); + gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); + gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); + + // CLEAR button + GtkWidget *clear_button = gtk_button_new_with_label("Clear"); + gtk_container_set_border_width (GTK_CONTAINER(clear_button), 2); + g_signal_connect (clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), clear_button, 0,1,1,1); + + // CENTER button + GtkWidget *center_button = gtk_button_new_with_label("Center"); + gtk_container_set_border_width (GTK_CONTAINER(center_button), 2); + g_signal_connect (center_button, "clicked", G_CALLBACK(handle_center_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), center_button, 1,1,1,1); + + // Tic waveform area + w->tic_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->tic_drawing_area, 400, 100); + g_signal_connect (w->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), w); + gtk_widget_set_events(w->tic_drawing_area, GDK_EXPOSURE_MASK); + gtk_widget_set_hexpand(w->tic_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->tic_drawing_area, TRUE); + gtk_container_add (GTK_CONTAINER(right_grid), w->tic_drawing_area); + + // Toc waveform area + w->toc_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->toc_drawing_area, 400, 100); + g_signal_connect (w->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), w); + gtk_widget_set_events(w->toc_drawing_area, GDK_EXPOSURE_MASK); + gtk_widget_set_hexpand(w->toc_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->toc_drawing_area, TRUE); + gtk_container_add (GTK_CONTAINER(right_grid), w->toc_drawing_area); + + // Period waveform area + w->period_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->period_drawing_area, 400, 100); + g_signal_connect (w->period_drawing_area, "draw", G_CALLBACK(period_draw_event), w); + gtk_widget_set_events(w->period_drawing_area, GDK_EXPOSURE_MASK); + gtk_widget_set_hexpand(w->period_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->period_drawing_area, TRUE); + gtk_container_add (GTK_CONTAINER(right_grid), w->period_drawing_area); + + + // Populate the root grid with the grids we created above + GtkWidget *root_grid = gtk_grid_new(); // The grid containing all of the UI + gtk_orientable_set_orientation (GTK_ORIENTABLE (root_grid), GTK_ORIENTATION_VERTICAL); + gtk_grid_set_row_spacing(GTK_GRID(root_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(root_grid), 10); + gtk_container_add(GTK_CONTAINER(w->window), root_grid); // Add the root grid to the window + + gtk_container_add (GTK_CONTAINER(root_grid), settings_grid); + gtk_container_add (GTK_CONTAINER(root_grid), w->info_drawing_area); + gtk_container_add (GTK_CONTAINER(root_grid), panes); + + // All done. Show all the widgets. + gtk_widget_show_all (w->window); + gtk_window_set_focus (GTK_WINDOW(w->window), NULL); // Unsets the focus widget (not working atm) + + // gtk_window_maximize(GTK_WINDOW(w->window)); } -/* Start up the app loop */ -int run_interface() -{ - int nominal_sr; - double real_sr; - - // Initialize audio - if(start_portaudio(&nominal_sr, &real_sr)) return 1; - - struct processing_buffers p[NSTEPS]; - int i; - for(i=0; i Date: Sun, 20 Dec 2015 23:24:51 +0100 Subject: [PATCH 02/25] Fix renamed cairo context variable in debug. --- interface.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/interface.c b/interface.c index 5099999..1d194a7 100644 --- a/interface.c +++ b/interface.c @@ -517,10 +517,10 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) else { char s[100]; sprintf(s," %.2f fps",1./g_timer_elapsed(timer, NULL)); - cairo_set_source(c, white); - cairo_set_font_size(c, OUTPUT_FONT); - cairo_move_to(c,x,y); - cairo_show_text(c,s); + cairo_set_source(cr, white); + cairo_set_font_size(cr, OUTPUT_FONT); + cairo_move_to(cr,x,y); + cairo_show_text(cr,s); g_timer_reset(timer); } } @@ -1000,17 +1000,15 @@ void init_main_window(struct main_window *w) // All done. Show all the widgets. gtk_widget_show_all (w->window); - gtk_window_set_focus (GTK_WINDOW(w->window), NULL); // Unsets the focus widget (not working atm) - + + // gtk_window_set_interactive_debugging(TRUE); // gtk_window_maximize(GTK_WINDOW(w->window)); } -/* Called when the application starts running */ +/* Called when the GTK application starts running */ static void activate (GtkApplication* app, gpointer user_data) { - initialize_palette(); // Set up the color definitions we'll be using - int nominal_sr; double real_sr; @@ -1032,7 +1030,12 @@ static void activate (GtkApplication* app, gpointer user_data) w.bfs = p; w.old = NULL; w.window = gtk_application_window_new (app); - + + initialize_palette(); // Set up the color definitions we'll be using + + // Use the dark theme + g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); + // Set up GDK+ widgets for the UI init_main_window(&w); From a2fd76334652af83f42b32a294407bd95194dfe7 Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 21 Dec 2015 15:16:41 +0100 Subject: [PATCH 03/25] More comments added --- audio.c | 24 +++++++++------- interface.c | 81 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/audio.c b/audio.c index dd1c4bc..c5010f4 100644 --- a/audio.c +++ b/audio.c @@ -19,10 +19,11 @@ #include "tg.h" #include -volatile float pa_buffers[2][PA_BUFF_SIZE]; -volatile int write_pointer = 0; -volatile uint64_t timestamp = 0; +volatile float pa_buffers[2][PA_BUFF_SIZE]; // Buffers to store the audio sample data +volatile int write_pointer = 0; // Current write position in the buffers +volatile uint64_t timestamp = 0; // Running timestamp +/* Callback function that consume audio in response to requests from an active PortAudio stream */ int paudio_callback(const void *input_buffer, void *output_buffer, unsigned long frame_count, @@ -31,16 +32,18 @@ int paudio_callback(const void *input_buffer, void *data) { unsigned long i; + // Copy the sample data to pa_buffers[] (Mac mini gets 512 samples on each callback) for(i=0; i < frame_count; i++) { pa_buffers[0][write_pointer] = ((float *)input_buffer)[2*i]; pa_buffers[1][write_pointer] = ((float *)input_buffer)[2*i + 1]; if(write_pointer < PA_BUFF_SIZE - 1) write_pointer++; - else write_pointer = 0; - timestamp++; + else write_pointer = 0; // Wrap over when reaching the buffer end + timestamp++; // Increase timestamp for each frame TODO: Move outside loop? } return 0; } +/* Set up PA to continuously sample audio and store in buffers */ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) { PaStream *stream; @@ -51,6 +54,7 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) if(err!=paNoError) goto error; + // Open audio stream with 2 input channels & 0 output channels with an unspecified buffer size err = Pa_OpenDefaultStream(&stream,2,0,paFloat32,PA_SAMPLE_RATE,paFramesPerBufferUnspecified,paudio_callback,x); *x = stream; if(err!=paNoError) @@ -66,7 +70,7 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) *real_sample_rate = info->sampleRate / 2; #else *nominal_sample_rate = PA_SAMPLE_RATE; - *real_sample_rate = info->sampleRate; + *real_sample_rate = info->sampleRate; // Actual sample rate, reported by PortAudio #endif debug("sample rate: nominal = %d real = %f\n",*nominal_sample_rate,*real_sample_rate); @@ -80,11 +84,11 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) int analyze_pa_data(struct processing_buffers *p, int bph, uint64_t events_from) { static uint64_t last_tic = 0; - int wp = write_pointer; + int wp = write_pointer; // Current write position in the buffers uint64_t ts = timestamp; - if(wp < 0 || wp >= PA_BUFF_SIZE) wp = 0; + if(wp < 0 || wp >= PA_BUFF_SIZE) wp = 0; // Sanity check for the buffer pointer #ifdef LIGHT - if(wp % 2) wp--; + if(wp % 2) wp--; // Make sure pointer is even. ts /= 2; #endif int i; @@ -97,7 +101,7 @@ int analyze_pa_data(struct processing_buffers *p, int bph, uint64_t events_from) #endif if(k < 0) k += PA_BUFF_SIZE; for(j=0; j < p[i].sample_count; j++) { - p[i].samples[j] = pa_buffers[0][k] + pa_buffers[1][k]; + p[i].samples[j] = pa_buffers[0][k] + pa_buffers[1][k]; // Merge stereo into mono(?) #ifdef LIGHT k += 2; #else diff --git a/interface.c b/interface.c index 1d194a7..9b699e9 100644 --- a/interface.c +++ b/interface.c @@ -135,18 +135,19 @@ int guess_bph(double period) return preset_bph[ret]; } +/* Get data results and if it's current or old */ struct processing_buffers *get_data(struct main_window *w, int *old) { struct processing_buffers *p = w->bfs; int i; for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); - if(i>=0) { + if(i >= 0) { if(w->old) pb_destroy_clone(w->old); w->old = pb_clone(&p[i]); *old = 0; return &p[i]; - } else { + } else { // Mark as old *old = 1; return w->old; } @@ -175,19 +176,19 @@ gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) return FALSE; } -/* Draw the actual waveform */ -void draw_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) +/* Draw the audio waveform */ +void draw_graph(double a, double b, cairo_t *cr, struct processing_buffers *p, GtkWidget *da) { int width = gtk_widget_get_allocated_width(da); int height = gtk_widget_get_allocated_height(da); int n; - int first = 1; + int first = TRUE; for(n=0; n<2*width; n++) { int i = n < width ? n : 2*width - 1 - n; double x = fmod(a + i * (b-a) / width, p->period); - if(x < 0) x += p->period; + if (x < 0) x += p->period; int j = floor(x); double y; @@ -195,13 +196,13 @@ void draw_graph(double a, double b, cairo_t *c, struct processing_buffers *p, Gt else y = p->waveform[j] * 0.4 / p->waveform_max; int k = round(y*height); - if(n < width) k = -k; + if (n < width) k = -k; - if(first) { - cairo_move_to(c,i+.5,height/2+k+.5); - first = 0; + if (first) { // TODO: Same code in both cases? + cairo_move_to(cr, i+.5, height/2+k+.5); + first = FALSE; } else - cairo_line_to(c,i+.5,height/2+k+.5); + cairo_line_to(cr, i+.5, height/2+k+.5); } } @@ -279,6 +280,7 @@ double get_rate(int bph, double sample_rate, struct processing_buffers *p) return (7200/(bph*p->period / sample_rate) - 1)*24*3600; } +/* Calculate the amplitude from the lift angle and audio signal */ double get_amplitude(double la, struct processing_buffers *p) { double ret = -1; @@ -329,13 +331,15 @@ void draw_waveform( int width = gtk_widget_get_allocated_width(da); int height = gtk_widget_get_allocated_height(da); - int fontsize = gtk_widget_get_allocated_width(w->window) / 90; + // Calculate font size for the amplitude and timing text on the grid + int fontsize = gtk_widget_get_allocated_width(w->window) / 90; // TODO: Better sizing. Just keep it at 12? if(fontsize < 12) fontsize = 12; - int i; - cairo_set_font_size(cr, fontsize); + int i; + + // Draw vertical time lines every ms for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); cairo_move_to(cr, x + .5, height / 2 + .5); @@ -346,41 +350,46 @@ void draw_waveform( cairo_set_source(cr, red); cairo_stroke(cr); } + + // Draw numbers for time scale, every 5 ms cairo_set_source(cr, white); for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { if(!(i%5)) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); char s[10]; - sprintf(s,"%d",i); - cairo_move_to(cr,x+fontsize/4, height-fontsize/2); - cairo_show_text(cr,s); + sprintf(s, "%d", i); + cairo_move_to(cr, x+fontsize/4, height-fontsize/2); + cairo_show_text(cr, s); } } cairo_text_extents_t extents; - cairo_text_extents(cr,"ms", &extents); - cairo_move_to(cr,width - extents.x_advance - fontsize/4, height-fontsize/2); + // Draw "ms" label + cairo_text_extents(cr, "ms", &extents); + cairo_move_to(cr, width - extents.x_advance - fontsize/4, height-fontsize/2); cairo_show_text(cr, "ms"); int old; struct processing_buffers *p = get_data(w, &old); double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; - for(i = 10; i < 360; i+=10) { - if(2*i < w->la) continue; + // Draw vertical amplitude lines + for (i = 10; i < 360; i+=10) { + if (2*i < w->la) continue; double t = period*amplitude_to_time(w->la, i); - if(t > .001 * NEGATIVE_SPAN) continue; + if (t > .001 * NEGATIVE_SPAN) continue; int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); cairo_move_to(cr, x+.5, .5); cairo_line_to(cr, x+.5, height / 2 + .5); - if(i % 50) + if (i % 50) cairo_set_source(cr,green); else cairo_set_source(cr,red); cairo_stroke(cr); } + // Draw numbers for amplitude scale double last_x = 0; cairo_set_source(cr, white); for(i = 50; i < 360; i+=50) { @@ -398,11 +407,13 @@ void draw_waveform( } } + // Draw "deg" label cairo_text_extents(cr, "deg", &extents); cairo_move_to(cr,width - extents.x_advance - fontsize/4, fontsize * 3 / 2); cairo_show_text(cr,"deg"); - if(p) { + // Draw audio waveform + if (p) { double span = 0.001 * w->sample_rate; int offset = get_offset(p); @@ -411,12 +422,14 @@ void draw_waveform( draw_graph(a,b,cr,p,da); + // Make the audio waveform yellow if it's not current. cairo_set_source(cr, old?yellow:white); cairo_stroke_preserve(cr); cairo_fill(cr); double pulse = get_pulse(p); - if(pulse > 0) { + if (pulse > 0) { + // Draw vertical blue line at start of pulse int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); cairo_move_to(cr, x, 1); cairo_line_to(cr, x, height - 1); @@ -424,7 +437,7 @@ void draw_waveform( cairo_set_line_width(cr, 2); cairo_stroke(cr); } - } else { + } else { // If no data, just draw the center line in yellow cairo_move_to(cr, .5, height / 2 + .5); cairo_line_to(cr, width - .5, height / 2 + .5); cairo_set_source(cr, yellow); @@ -433,21 +446,25 @@ void draw_waveform( } +/* Used as a function parameter in draw_waveform() */ int get_tic(struct processing_buffers *p) { return p->tic; } +/* Used as a function parameter in draw_waveform() */ int get_toc(struct processing_buffers *p) { return p->toc; } +/* Used as a function parameter in draw_waveform() */ double get_tic_pulse(struct processing_buffers *p) { return p->tic_pulse; } +/* Used as a function parameter in draw_waveform() */ double get_toc_pulse(struct processing_buffers *p) { return p->toc_pulse; @@ -888,7 +905,7 @@ void init_main_window(struct main_window *w) w->debug_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->debug_drawing_area, 500, 100); g_signal_connect (w->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), w); - gtk_widget_set_events(w->debug_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->debug_drawing_area, GDK_EXPOSURE_MASK); gtk_container_add (GTK_CONTAINER(settings_grid), w->debug_drawing_area); printf("DEBUG!\n"); @@ -898,7 +915,7 @@ void init_main_window(struct main_window *w) w->info_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->info_drawing_area, 720, OUTPUT_WINDOW_HEIGHT); g_signal_connect (w->info_drawing_area, "draw", G_CALLBACK(info_draw_event), w); - gtk_widget_set_events(w->info_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->info_drawing_area, GDK_EXPOSURE_MASK); // Populate the panes GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); @@ -942,7 +959,7 @@ void init_main_window(struct main_window *w) w->paperstrip_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) g_signal_connect (w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); - gtk_widget_set_events(w->paperstrip_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->paperstrip_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); @@ -963,7 +980,7 @@ void init_main_window(struct main_window *w) w->tic_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->tic_drawing_area, 400, 100); g_signal_connect (w->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), w); - gtk_widget_set_events(w->tic_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->tic_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->tic_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->tic_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->tic_drawing_area); @@ -972,7 +989,7 @@ void init_main_window(struct main_window *w) w->toc_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->toc_drawing_area, 400, 100); g_signal_connect (w->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), w); - gtk_widget_set_events(w->toc_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->toc_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->toc_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->toc_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->toc_drawing_area); @@ -981,7 +998,7 @@ void init_main_window(struct main_window *w) w->period_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->period_drawing_area, 400, 100); g_signal_connect (w->period_drawing_area, "draw", G_CALLBACK(period_draw_event), w); - gtk_widget_set_events(w->period_drawing_area, GDK_EXPOSURE_MASK); + // gtk_widget_set_events(w->period_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->period_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->period_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->period_drawing_area); From abcc6ef16cc0e00237843d836b42699d3aee9e60 Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 21 Dec 2015 15:26:30 +0100 Subject: [PATCH 04/25] Replaced sprintf() with snprintf() --- interface.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/interface.c b/interface.c index 9b699e9..c476981 100644 --- a/interface.c +++ b/interface.c @@ -357,7 +357,7 @@ void draw_waveform( if(!(i%5)) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); char s[10]; - sprintf(s, "%d", i); + snprintf(s, 10, "%d", i); cairo_move_to(cr, x+fontsize/4, height-fontsize/2); cairo_show_text(cr, s); } @@ -398,8 +398,7 @@ void draw_waveform( int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); if(x > last_x) { char s[10]; - - sprintf(s,"%d",abs(i)); + snprintf(s, 10, "%d", abs(i)); cairo_move_to(cr, x + fontsize/4, fontsize * 3 / 2); cairo_show_text(cr, s); cairo_text_extents(cr, s, &extents); @@ -487,11 +486,11 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) double be = fabs(p->be) * 1000 / p->sample_rate; double amp = get_amplitude(w->la, p); char rates[20]; - sprintf(rates,"%s%d",rate > 0 ? "+" : rate < 0 ? "-" : "",abs(rate)); - sprintf(outputs[0],"%4s",rates); - sprintf(outputs[2]," %4.1f",be); + snprintf(rates, 20, "%s%d", rate > 0 ? "+" : rate < 0 ? "-" : "", abs(rate)); + snprintf(outputs[0], 20, "%4s",rates); + snprintf(outputs[2], 20, " %4.1f",be); if(amp > 0) - sprintf(outputs[4]," %3.0f",amp); + snprintf(outputs[4], 20, " %3.0f",amp); else strcpy(outputs[4]," ---"); } else { @@ -499,7 +498,7 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) strcpy(outputs[2]," ----"); strcpy(outputs[4]," ---"); } - sprintf(outputs[6]," %d",w->guessed_bph); + snprintf(outputs[6], 20, " %d",w->guessed_bph); strcpy(outputs[1]," s/d"); strcpy(outputs[3]," ms"); @@ -532,8 +531,8 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) static GTimer *timer = NULL; if (!timer) timer = g_timer_new(); else { - char s[100]; - sprintf(s," %.2f fps",1./g_timer_elapsed(timer, NULL)); + char s[50]; + snprintf(s, 50, " %.2f fps",1./g_timer_elapsed(timer, NULL)); cairo_set_source(cr, white); cairo_set_font_size(cr, OUTPUT_FONT); cairo_move_to(cr,x,y); @@ -764,10 +763,10 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo fontsize = 12; cairo_set_font_size(cr, fontsize); - char s[100]; + char s[50]; cairo_text_extents_t extents; - sprintf(s, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); + snprintf(s, 50, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); cairo_text_extents(cr,s,&extents); cairo_move_to(cr, (width - extents.x_advance)/2, height - 30); cairo_show_text(cr, s); @@ -881,8 +880,8 @@ void init_main_window(struct main_window *w) gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "Auto"); int *bph; for(bph = preset_bph; *bph; bph++) { - char s[100]; - sprintf(s,"%d", *bph); + char s[50]; + snprintf(s, 50, "%d", *bph); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); } gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); From 612898b73d15e3ea1ef53f16fa8ef90b69890d5e Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 21 Dec 2015 21:05:00 +0100 Subject: [PATCH 05/25] Added CSS styling --- interface.c | 126 ++++++++++++++++++++++++++++++---------------------- tg.css | 23 ++++++++++ 2 files changed, 97 insertions(+), 52 deletions(-) create mode 100644 tg.css diff --git a/interface.c b/interface.c index c476981..7012d9d 100644 --- a/interface.c +++ b/interface.c @@ -22,7 +22,7 @@ int preset_bph[] = PRESET_BPH; -cairo_pattern_t *black,*white,*red,*green,*blue,*blueish,*yellow; +cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color, *icon1, *icon2, *text; void print_debug(char *format,...) { @@ -58,20 +58,39 @@ void error(char *format,...) gtk_widget_destroy(dialog); } -void define_color(cairo_pattern_t **gc,double r,double g,double b) +void define_color(cairo_pattern_t **gc, GdkRGBA col) { - *gc = cairo_pattern_create_rgb(r,g,b); + *gc = cairo_pattern_create_rgb(col.red, col.green, col.blue); } -void initialize_palette() +/* Grab color definitions from css */ +void initialize_palette(GtkWidget *win) { - define_color(&black,0,0,0); - define_color(&white,1,1,1); - define_color(&red,1,0,0); - define_color(&green,0,0.8,0); - define_color(&blue,0,0,1); - define_color(&blueish,0,0,.5); - define_color(&yellow,1,1,0); + GtkStyleContext *sc = gtk_widget_get_style_context(win); + + GdkRGBA color; + + gtk_style_context_lookup_color (sc, "graph_background", &color); + define_color(&bg_color, color); + gtk_style_context_lookup_color (sc, "waveform_active", &color); + define_color(&waveform_color, color); + gtk_style_context_lookup_color (sc, "waveform_stopped", &color); + define_color(&stopped_color, color); + gtk_style_context_lookup_color (sc, "grid_line", &color); + define_color(&grid_color, color); + gtk_style_context_lookup_color (sc, "grid_line_alternate", &color); + define_color(&grid2_color, color); + gtk_style_context_lookup_color (sc, "pulse", &color); + define_color(&pulse_color, color); + gtk_style_context_lookup_color (sc, "pulse_range", &color); + define_color(&range_color, color); + + gtk_style_context_lookup_color (sc, "text", &color); + define_color(&text, color); + gtk_style_context_lookup_color (sc, "icon_on", &color); + define_color(&icon1, color); + gtk_style_context_lookup_color (sc, "icon_off", &color); + define_color(&icon2, color); } struct main_window { @@ -252,7 +271,7 @@ double draw_watch_icon(cairo_t *c, int signal) { int happy = !!signal; cairo_set_line_width(c,3); - cairo_set_source(c,happy?green:red); + cairo_set_source(c, happy ? icon1 : icon2); cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); @@ -298,7 +317,7 @@ void cairo_init(cairo_t *c) { cairo_set_line_width(c, 1); - cairo_set_source(c,black); + cairo_set_source(c, bg_color); cairo_paint(c); } @@ -345,14 +364,14 @@ void draw_waveform( cairo_move_to(cr, x + .5, height / 2 + .5); cairo_line_to(cr, x + .5, height - .5); if(i%5) - cairo_set_source(cr, green); + cairo_set_source(cr, grid_color); else - cairo_set_source(cr, red); + cairo_set_source(cr, grid2_color); cairo_stroke(cr); } // Draw numbers for time scale, every 5 ms - cairo_set_source(cr, white); + cairo_set_source(cr, text); for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { if(!(i%5)) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); @@ -383,15 +402,15 @@ void draw_waveform( cairo_move_to(cr, x+.5, .5); cairo_line_to(cr, x+.5, height / 2 + .5); if (i % 50) - cairo_set_source(cr,green); + cairo_set_source(cr, grid_color); else - cairo_set_source(cr,red); + cairo_set_source(cr, grid2_color); cairo_stroke(cr); } // Draw numbers for amplitude scale double last_x = 0; - cairo_set_source(cr, white); + cairo_set_source(cr, text); for(i = 50; i < 360; i+=50) { double t = period*amplitude_to_time(w->la, i); if(t > .001 * NEGATIVE_SPAN) continue; @@ -422,7 +441,7 @@ void draw_waveform( draw_graph(a,b,cr,p,da); // Make the audio waveform yellow if it's not current. - cairo_set_source(cr, old?yellow:white); + cairo_set_source(cr, old ? stopped_color : waveform_color); cairo_stroke_preserve(cr); cairo_fill(cr); @@ -432,14 +451,14 @@ void draw_waveform( int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); cairo_move_to(cr, x, 1); cairo_line_to(cr, x, height - 1); - cairo_set_source(cr, blue); + cairo_set_source(cr, pulse_color); cairo_set_line_width(cr, 2); cairo_stroke(cr); } } else { // If no data, just draw the center line in yellow cairo_move_to(cr, .5, height / 2 + .5); cairo_line_to(cr, width - .5, height / 2 + .5); - cairo_set_source(cr, yellow); + cairo_set_source(cr, stopped_color); cairo_stroke(cr); } @@ -514,14 +533,14 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) int i; for(i=0; i <8; i++) { if(i%2) { - cairo_set_source(cr, white); + cairo_set_source(cr, text); cairo_move_to(cr,x,y); cairo_set_font_size(cr, OUTPUT_FONT*2/3); cairo_show_text(cr,outputs[i]); cairo_text_extents(cr,outputs[i],&extents); x += extents.x_advance; } else { - cairo_set_source(cr, i > 4 || !p || !old ? white : yellow); + cairo_set_source(cr, i > 4 || !p || !old ? text : stopped_color); cairo_set_font_size(cr, OUTPUT_FONT); x = print_number(cr,x,y,outputs[i]); } @@ -533,7 +552,7 @@ gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) else { char s[50]; snprintf(s, 50, " %.2f fps",1./g_timer_elapsed(timer, NULL)); - cairo_set_source(cr, white); + cairo_set_source(cr, text); cairo_set_font_size(cr, OUTPUT_FONT); cairo_move_to(cr,x,y); cairo_show_text(cr,s); @@ -578,14 +597,14 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w cairo_line_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(cr, blueish); + cairo_set_source(cr, range_color); cairo_fill(cr); cairo_move_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); cairo_line_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(cr, blueish); + cairo_set_source(cr, range_color); cairo_fill(cr); } @@ -595,22 +614,22 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w cairo_move_to(cr, x+.5, .5); cairo_line_to(cr, x+.5, height - .5); if(i % 4) - cairo_set_source(cr, green); + cairo_set_source(cr, grid_color); else - cairo_set_source(cr, red); + cairo_set_source(cr, grid2_color); cairo_stroke(cr); } if(p) { draw_graph(a,b,cr,p,w->period_drawing_area); - cairo_set_source(cr, old?yellow:white); + cairo_set_source(cr, old ? stopped_color : waveform_color); cairo_stroke_preserve(cr); cairo_fill(cr); } else { cairo_move_to(cr, .5, height / 2 + .5); cairo_line_to(cr, width - .5, height / 2 + .5); - cairo_set_source(cr, yellow); + cairo_set_source(cr, stopped_color); cairo_stroke(cr); } @@ -681,7 +700,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo } } } - cairo_set_source(cr, blue); + cairo_set_source(cr, pulse_color); cairo_stroke(cr); } } @@ -694,7 +713,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_line_to(cr, left_margin + .5, height - .5); cairo_move_to(cr, right_margin + .5, .5); cairo_line_to(cr, right_margin + .5, height - .5); - cairo_set_source(cr, green); + cairo_set_source(cr, grid_color); cairo_stroke(cr); double sweep = w->sample_rate * 3600. / w->guessed_bph; @@ -707,11 +726,11 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo if(y > height) break; cairo_move_to(cr, .5, y); cairo_line_to(cr, width-.5, y); - cairo_set_source(cr, (last_tenth-i)%6 ? green : red); + cairo_set_source(cr, (last_tenth-i)%6 ? grid_color : grid2_color); cairo_stroke(cr); } - cairo_set_source(cr, stopped?yellow:white); + cairo_set_source(cr, stopped ? stopped_color : waveform_color); for(i = w->events_wp;;) { if(!w->events[i]) break; double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); @@ -739,7 +758,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo } // Draw the arrowed line for the ms scale at the bottom - cairo_set_source(cr, white); + cairo_set_source(cr, text); cairo_set_line_width(cr, 2); cairo_move_to(cr, left_margin + 3, height - 20.5); cairo_line_to(cr, right_margin - 3, height - 20.5); @@ -788,7 +807,7 @@ gboolean debug_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) draw_debug_graph(a,b,cr,p,w->debug_drawing_area); - cairo_set_source(cr, old?yellow:white); + cairo_set_source(cr, stopped ? stopped_color : waveform_color); cairo_stroke(cr); } @@ -920,19 +939,6 @@ void init_main_window(struct main_window *w) GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); gtk_paned_set_wide_handle(GTK_PANED(panes), TRUE); - /* Clunky way to make the pane handle bigger, but seems to be the only option - otherwise the max is 5px with gtk_paned_set_wide_handle() */ - GtkStyleContext *sc = gtk_widget_get_style_context(panes); - GtkCssProvider *provider = gtk_css_provider_new(); - gtk_css_provider_load_from_data(provider, "* {\n" - " -GtkPaned-handle-size: 10;\n" - " }\n" - , -1, NULL); - - gtk_style_context_add_provider(sc, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - g_object_unref (provider); - - GtkWidget *left_grid = gtk_grid_new(); GtkWidget *right_grid = gtk_grid_new(); @@ -965,12 +971,14 @@ void init_main_window(struct main_window *w) // CLEAR button GtkWidget *clear_button = gtk_button_new_with_label("Clear"); + gtk_widget_set_name(clear_button, "clear_button"); // To allow for CSS styling gtk_container_set_border_width (GTK_CONTAINER(clear_button), 2); g_signal_connect (clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); gtk_grid_attach(GTK_GRID(left_grid), clear_button, 0,1,1,1); // CENTER button GtkWidget *center_button = gtk_button_new_with_label("Center"); + gtk_widget_set_name(center_button, "center_button"); // To allow for CSS styling gtk_container_set_border_width (GTK_CONTAINER(center_button), 2); g_signal_connect (center_button, "clicked", G_CALLBACK(handle_center_trace), w); gtk_grid_attach(GTK_GRID(left_grid), center_button, 1,1,1,1); @@ -1047,11 +1055,25 @@ static void activate (GtkApplication* app, gpointer user_data) w.old = NULL; w.window = gtk_application_window_new (app); - initialize_palette(); // Set up the color definitions we'll be using - // Use the dark theme g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); + /* Clunky way to make the pane handle bigger, but seems to be the only option + otherwise the max is 5px with gtk_paned_set_wide_handle() */ + + GtkCssProvider *provider = gtk_css_provider_new(); + GFile *css_file = g_file_new_for_commandline_arg("tg.css"); + gtk_css_provider_load_from_file(provider, css_file, NULL); // No error handling + + GdkDisplay *display = gdk_display_get_default(); + GdkScreen *screen = gdk_display_get_default_screen(display); + gtk_style_context_add_provider_for_screen( screen ,GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_object_unref(provider); + g_object_unref(css_file); + + initialize_palette(w.window); // Set up the color definitions we'll be using + // Set up GDK+ widgets for the UI init_main_window(&w); diff --git a/tg.css b/tg.css new file mode 100644 index 0000000..23dc373 --- /dev/null +++ b/tg.css @@ -0,0 +1,23 @@ +@define-color graph_background rgb(0, 0, 0); /* black */ +@define-color grid_line rgb(0, 80%, 0); /* green */ +@define-color grid_line_alternate rgb(100%, 0, 0); /* red */ +@define-color waveform_active rgb(255, 255, 255); /* white */ +@define-color waveform_stopped rgb(255, 255, 0); /* yellow */ +@define-color pulse rgb(0, 0, 100%); /* blue */ +@define-color pulse_range rgb(0, 0, 50%); /* blueish */ +@define-color icon_on rgb(0, 100%, 0); /* green */ +@define-color icon_off rgb(100%, 0, 0); /* red */ +@define-color text rgb(255, 255, 255); /* white */ + + +* { + -GtkPaned-handle-size: 10; +} + +#clear_button { + /* font-size: 16px; */ +} + +#center_button { + /* font-size: 16px; */ +} From 1f8687e4c45c574bab487d615d2a9b9e590e4a36 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 23 Dec 2015 01:53:31 +0100 Subject: [PATCH 06/25] More comments added. --- algo.c | 4 ++- audio.c | 2 +- interface.c | 82 ++++++++++++++++++++++++++--------------------------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/algo.c b/algo.c index 7d8af1a..5f0fa7f 100644 --- a/algo.c +++ b/algo.c @@ -95,18 +95,20 @@ void setup_buffers(struct processing_buffers *b) #endif } +/* Create a copy of a processing buffer */ struct processing_buffers *pb_clone(struct processing_buffers *p) { struct processing_buffers *new = malloc(sizeof(struct processing_buffers)); new->sample_rate = p->sample_rate; new->waveform = malloc(new->sample_rate * sizeof(float)); - memcpy(new->waveform, p->waveform, new->sample_rate * sizeof(float)); + memcpy(new->waveform, p->waveform, new->sample_rate * sizeof(float)); // Copy over waveform data #ifdef DEBUG new->debug = malloc(p->sample_count * sizeof(float)); memcpy(new->debug, p->debug, p->sample_count * sizeof(float)); #endif + // Copy over the rest of the variables new->sample_count = p->sample_count; new->period = p->period; new->sigma = p->sigma; diff --git a/audio.c b/audio.c index c5010f4..3dd19a3 100644 --- a/audio.c +++ b/audio.c @@ -54,7 +54,7 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) if(err!=paNoError) goto error; - // Open audio stream with 2 input channels & 0 output channels with an unspecified buffer size + // Open an audio stream with 2 input channels & 0 output channels with an unspecified buffer size err = Pa_OpenDefaultStream(&stream,2,0,paFloat32,PA_SAMPLE_RATE,paFramesPerBufferUnspecified,paudio_callback,x); *x = stream; if(err!=paNoError) diff --git a/interface.c b/interface.c index 7012d9d..4b289cc 100644 --- a/interface.c +++ b/interface.c @@ -22,7 +22,7 @@ int preset_bph[] = PRESET_BPH; -cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color, *icon1, *icon2, *text; +cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color,*icon1,*icon2,*text; void print_debug(char *format,...) { @@ -58,6 +58,7 @@ void error(char *format,...) gtk_widget_destroy(dialog); } +/* Convert a GTK color object into a Cairo object for drawing */ void define_color(cairo_pattern_t **gc, GdkRGBA col) { *gc = cairo_pattern_create_rgb(col.red, col.green, col.blue); @@ -70,26 +71,26 @@ void initialize_palette(GtkWidget *win) GdkRGBA color; - gtk_style_context_lookup_color (sc, "graph_background", &color); + gtk_style_context_lookup_color(sc, "graph_background", &color); define_color(&bg_color, color); - gtk_style_context_lookup_color (sc, "waveform_active", &color); + gtk_style_context_lookup_color(sc, "waveform_active", &color); define_color(&waveform_color, color); - gtk_style_context_lookup_color (sc, "waveform_stopped", &color); + gtk_style_context_lookup_color(sc, "waveform_stopped", &color); define_color(&stopped_color, color); - gtk_style_context_lookup_color (sc, "grid_line", &color); + gtk_style_context_lookup_color(sc, "grid_line", &color); define_color(&grid_color, color); - gtk_style_context_lookup_color (sc, "grid_line_alternate", &color); + gtk_style_context_lookup_color(sc, "grid_line_alternate", &color); define_color(&grid2_color, color); - gtk_style_context_lookup_color (sc, "pulse", &color); + gtk_style_context_lookup_color(sc, "pulse", &color); define_color(&pulse_color, color); - gtk_style_context_lookup_color (sc, "pulse_range", &color); + gtk_style_context_lookup_color(sc, "pulse_range", &color); define_color(&range_color, color); - gtk_style_context_lookup_color (sc, "text", &color); + gtk_style_context_lookup_color(sc, "text", &color); define_color(&text, color); - gtk_style_context_lookup_color (sc, "icon_on", &color); + gtk_style_context_lookup_color(sc, "icon_on", &color); define_color(&icon1, color); - gtk_style_context_lookup_color (sc, "icon_off", &color); + gtk_style_context_lookup_color(sc, "icon_off", &color); define_color(&icon2, color); } @@ -154,7 +155,7 @@ int guess_bph(double period) return preset_bph[ret]; } -/* Get data results and if it's current or old */ +/* Get data results and a flag indicating if it's current or old */ struct processing_buffers *get_data(struct main_window *w, int *old) { struct processing_buffers *p = w->bfs; @@ -162,8 +163,9 @@ struct processing_buffers *get_data(struct main_window *w, int *old) for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); if(i >= 0) { - if(w->old) pb_destroy_clone(w->old); - w->old = pb_clone(&p[i]); + // TODO: Can't we have one object kept alive instead of destroying/creating new ones every time? + if(w->old) pb_destroy_clone(w->old); // Remove the previous old data + w->old = pb_clone(&p[i]); // Store the current data as old *old = 0; return &p[i]; } else { // Mark as old @@ -176,7 +178,7 @@ void recompute(struct main_window *w) { w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); int old; - struct processing_buffers *p = get_data(w,&old); + struct processing_buffers *p = get_data(w, &old); if(old) w->signal = -w->signal; if(p) w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); @@ -192,6 +194,8 @@ guint refresh_window(struct main_window *w) gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { + // If you return FALSE in the "delete-event" signal handler + // GTK will emit the "destroy" signal. return FALSE; } @@ -352,7 +356,7 @@ void draw_waveform( // Calculate font size for the amplitude and timing text on the grid int fontsize = gtk_widget_get_allocated_width(w->window) / 90; // TODO: Better sizing. Just keep it at 12? - if(fontsize < 12) + if (fontsize < 12) fontsize = 12; cairo_set_font_size(cr, fontsize); @@ -389,7 +393,7 @@ void draw_waveform( cairo_move_to(cr, width - extents.x_advance - fontsize/4, height-fontsize/2); cairo_show_text(cr, "ms"); - int old; + int old; // Flag set if the data isn't current struct processing_buffers *p = get_data(w, &old); double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; @@ -455,7 +459,7 @@ void draw_waveform( cairo_set_line_width(cr, 2); cairo_stroke(cr); } - } else { // If no data, just draw the center line in yellow + } else { // If no data, just draw the center line cairo_move_to(cr, .5, height / 2 + .5); cairo_line_to(cr, width - .5, height / 2 + .5); cairo_set_source(cr, stopped_color); @@ -673,21 +677,22 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo int strip_width = round(width / (1 + PAPERSTRIP_MARGIN)); + // Draw the 4 directional lines in the paperstrip cairo_set_line_width(cr, 1.3); - if(p && w->events[w->events_wp]) { + if (p && w->events[w->events_wp]) { double rate = get_rate(w->guessed_bph, w->sample_rate, p); double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); - if(slope <= 1 && slope >= -1) { - for(i=0; i<4; i++) { + if (slope <= 1 && slope >= -1) { + for (i=0; i<4; i++) { double y = 0; cairo_move_to(cr, (double)width * (i+.5) / 4, 0); for(;;) { double x = y * slope + (double)width * (i+.5) / 4; x = fmod(x, width); - if(x < 0) x += width; + if (x < 0) x += width; double nx = x + slope * (height - y); - if(nx >= 0 && nx <= width) { + if (nx >= 0 && nx <= width) { cairo_line_to(cr, nx, height); break; } else { @@ -695,7 +700,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo y += d / fabs(slope); cairo_line_to(cr, slope > 0 ? width : 0, y); y += 1; - if(y > height) break; + if (y > height) break; cairo_move_to(cr, slope > 0 ? 0 : width, y); } } @@ -705,10 +710,12 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo } } - cairo_set_line_width(cr,1); + cairo_set_line_width(cr, 1); int left_margin = (width - strip_width) / 2; int right_margin = (width + strip_width) / 2; + + // Draw the 2 vertical lines at the margins of the paperstrip cairo_move_to(cr, left_margin + .5, .5); cairo_line_to(cr, left_margin + .5, height - .5); cairo_move_to(cr, right_margin + .5, .5); @@ -716,6 +723,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_set_source(cr, grid_color); cairo_stroke(cr); + // Draw the horizontal lines of the paperstrip double sweep = w->sample_rate * 3600. / w->guessed_bph; double now = sweep*ceil(time/sweep); double ten_s = w->sample_rate * 10 / sweep; @@ -730,6 +738,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_stroke(cr); } + // Plot the tick/tocks on the paperstrip cairo_set_source(cr, stopped ? stopped_color : waveform_color); for(i = w->events_wp;;) { if(!w->events[i]) break; @@ -775,8 +784,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_line_to(cr, right_margin + .5, height - 20.5); cairo_fill(cr); - // Write the ms scale at the bottom of the paperstrip - + // Draw the ms scale at the bottom of the paperstrip int fontsize = gtk_widget_get_allocated_width(w->window) / 90; if(fontsize < 12) fontsize = 12; @@ -880,7 +888,7 @@ void init_main_window(struct main_window *w) w->la = DEFAULT_LA; gtk_container_set_border_width(GTK_CONTAINER(w->window), 5); // Border around the window - g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); + g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); // Signal emitted if a user requests that a toplevel window is closed. g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); @@ -904,7 +912,7 @@ void init_main_window(struct main_window *w) gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); } gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); - gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus + gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus. Not working.... g_signal_connect (w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); gtk_container_add (GTK_CONTAINER(settings_grid), w->bph_combo_box); @@ -923,17 +931,14 @@ void init_main_window(struct main_window *w) w->debug_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->debug_drawing_area, 500, 100); g_signal_connect (w->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), w); - // gtk_widget_set_events(w->debug_drawing_area, GDK_EXPOSURE_MASK); gtk_container_add (GTK_CONTAINER(settings_grid), w->debug_drawing_area); - printf("DEBUG!\n"); #endif // Info area w->info_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->info_drawing_area, 720, OUTPUT_WINDOW_HEIGHT); g_signal_connect (w->info_drawing_area, "draw", G_CALLBACK(info_draw_event), w); - // gtk_widget_set_events(w->info_drawing_area, GDK_EXPOSURE_MASK); // Populate the panes GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); @@ -964,7 +969,6 @@ void init_main_window(struct main_window *w) w->paperstrip_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) g_signal_connect (w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); - // gtk_widget_set_events(w->paperstrip_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); @@ -987,7 +991,6 @@ void init_main_window(struct main_window *w) w->tic_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->tic_drawing_area, 400, 100); g_signal_connect (w->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), w); - // gtk_widget_set_events(w->tic_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->tic_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->tic_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->tic_drawing_area); @@ -996,7 +999,6 @@ void init_main_window(struct main_window *w) w->toc_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->toc_drawing_area, 400, 100); g_signal_connect (w->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), w); - // gtk_widget_set_events(w->toc_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->toc_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->toc_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->toc_drawing_area); @@ -1005,7 +1007,6 @@ void init_main_window(struct main_window *w) w->period_drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(w->period_drawing_area, 400, 100); g_signal_connect (w->period_drawing_area, "draw", G_CALLBACK(period_draw_event), w); - // gtk_widget_set_events(w->period_drawing_area, GDK_EXPOSURE_MASK); gtk_widget_set_hexpand(w->period_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->period_drawing_area, TRUE); gtk_container_add (GTK_CONTAINER(right_grid), w->period_drawing_area); @@ -1058,16 +1059,13 @@ static void activate (GtkApplication* app, gpointer user_data) // Use the dark theme g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); - /* Clunky way to make the pane handle bigger, but seems to be the only option - otherwise the max is 5px with gtk_paned_set_wide_handle() */ - GtkCssProvider *provider = gtk_css_provider_new(); GFile *css_file = g_file_new_for_commandline_arg("tg.css"); - gtk_css_provider_load_from_file(provider, css_file, NULL); // No error handling + gtk_css_provider_load_from_file(provider, css_file, NULL); // No error handling yet! GdkDisplay *display = gdk_display_get_default(); GdkScreen *screen = gdk_display_get_default_screen(display); - gtk_style_context_add_provider_for_screen( screen ,GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref(provider); g_object_unref(css_file); @@ -1080,8 +1078,8 @@ static void activate (GtkApplication* app, gpointer user_data) // Call refresh_window() 10 times/second g_timeout_add_full(G_PRIORITY_LOW, 100, (GSourceFunc)refresh_window, &w, NULL); + // All GTK applications must have a gtk_main(). Control ends here and waits for an event to occur. gtk_main(); // Runs the main loop until gtk_main_quit() is called. - } /* PROGRAM START */ From a6694b3db3f692abcf639966bfb6a2a89b6cfc9f Mon Sep 17 00:00:00 2001 From: Rob Date: Sat, 2 Jan 2016 23:06:44 +0100 Subject: [PATCH 07/25] Separate labels for info text. --- interface.c | 1838 ++++++++++++++++++++++++++------------------------- tg.css | 16 + 2 files changed, 956 insertions(+), 898 deletions(-) diff --git a/interface.c b/interface.c index 4b289cc..d1ad317 100644 --- a/interface.c +++ b/interface.c @@ -26,1073 +26,1115 @@ cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color, void print_debug(char *format,...) { - va_list args; - va_start(args,format); - vfprintf(stderr,format,args); - va_end(args); + va_list args; + va_start(args,format); + vfprintf(stderr,format,args); + va_end(args); } void error(char *format,...) { - char s[100]; - va_list args; - - va_start(args,format); - int size = vsnprintf(s,100,format,args); - va_end(args); - - char *t; - if(size < 100) { - t = s; - } else { - t = alloca(size+1); - va_start(args,format); - vsnprintf(t,size+1,format,args); - va_end(args); - } - - fprintf(stderr,"%s\n",t); - - GtkWidget *dialog = gtk_message_dialog_new(NULL,0,GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,"%s",t); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); + char s[100]; + va_list args; + + va_start(args,format); + int size = vsnprintf(s,100,format,args); + va_end(args); + + char *t; + if(size < 100) { + t = s; + } else { + t = alloca(size+1); + va_start(args,format); + vsnprintf(t,size+1,format,args); + va_end(args); + } + + fprintf(stderr,"%s\n",t); + + GtkWidget *dialog = gtk_message_dialog_new(NULL,0,GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,"%s",t); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); } /* Convert a GTK color object into a Cairo object for drawing */ void define_color(cairo_pattern_t **gc, GdkRGBA col) { - *gc = cairo_pattern_create_rgb(col.red, col.green, col.blue); + *gc = cairo_pattern_create_rgb(col.red, col.green, col.blue); } /* Grab color definitions from css */ void initialize_palette(GtkWidget *win) { - GtkStyleContext *sc = gtk_widget_get_style_context(win); - - GdkRGBA color; - - gtk_style_context_lookup_color(sc, "graph_background", &color); - define_color(&bg_color, color); - gtk_style_context_lookup_color(sc, "waveform_active", &color); - define_color(&waveform_color, color); - gtk_style_context_lookup_color(sc, "waveform_stopped", &color); - define_color(&stopped_color, color); - gtk_style_context_lookup_color(sc, "grid_line", &color); - define_color(&grid_color, color); - gtk_style_context_lookup_color(sc, "grid_line_alternate", &color); - define_color(&grid2_color, color); - gtk_style_context_lookup_color(sc, "pulse", &color); - define_color(&pulse_color, color); - gtk_style_context_lookup_color(sc, "pulse_range", &color); - define_color(&range_color, color); - - gtk_style_context_lookup_color(sc, "text", &color); - define_color(&text, color); - gtk_style_context_lookup_color(sc, "icon_on", &color); - define_color(&icon1, color); - gtk_style_context_lookup_color(sc, "icon_off", &color); - define_color(&icon2, color); + GtkStyleContext *sc = gtk_widget_get_style_context(win); + + GdkRGBA color; + + gtk_style_context_lookup_color(sc, "graph_background", &color); + define_color(&bg_color, color); + gtk_style_context_lookup_color(sc, "waveform_active", &color); + define_color(&waveform_color, color); + gtk_style_context_lookup_color(sc, "waveform_stopped", &color); + define_color(&stopped_color, color); + gtk_style_context_lookup_color(sc, "grid_line", &color); + define_color(&grid_color, color); + gtk_style_context_lookup_color(sc, "grid_line_alternate", &color); + define_color(&grid2_color, color); + gtk_style_context_lookup_color(sc, "pulse", &color); + define_color(&pulse_color, color); + gtk_style_context_lookup_color(sc, "pulse_range", &color); + define_color(&range_color, color); + + gtk_style_context_lookup_color(sc, "text", &color); + define_color(&text, color); + gtk_style_context_lookup_color(sc, "icon_on", &color); + define_color(&icon1, color); + gtk_style_context_lookup_color(sc, "icon_off", &color); + define_color(&icon2, color); } struct main_window { - GtkWidget *window; - GtkWidget *bph_combo_box; - GtkWidget *la_spin_button; - GtkWidget *info_drawing_area; - GtkWidget *tic_drawing_area; - GtkWidget *toc_drawing_area; - GtkWidget *period_drawing_area; - GtkWidget *paperstrip_drawing_area; + GtkWidget *window; + GtkWidget *bph_combo_box; + GtkWidget *la_spin_button; + GtkWidget *icon_drawing_area; + GtkWidget *rate_label; + GtkWidget *beaterror_label; + GtkWidget *amplitude_label; + GtkWidget *bph_label; + GtkWidget *tic_drawing_area; + GtkWidget *toc_drawing_area; + GtkWidget *period_drawing_area; + GtkWidget *paperstrip_drawing_area; #ifdef DEBUG - GtkWidget *debug_drawing_area; + GtkWidget *fps_label; + GtkWidget *debug_drawing_area; #endif - - struct processing_buffers *bfs; - struct processing_buffers *old; - - int bph; - int guessed_bph; - int last_bph; - double la; - double sample_rate; - - uint64_t *events; - int events_wp; - uint64_t events_from; - double trace_centering; - - int signal; + + struct processing_buffers *bfs; + struct processing_buffers *old; + + int bph; // User selected bph. 0 if "Automatic" + int guessed_bph; // Calculated bph + int last_bph; + double la; + double sample_rate; + + uint64_t *events; + int events_wp; + uint64_t events_from; + double trace_centering; + + int signal; }; +/* Redraw the DrawingArea widgets (waveforms etc.) */ void redraw(struct main_window *w) { - gtk_widget_queue_draw(w->info_drawing_area); - gtk_widget_queue_draw(w->tic_drawing_area); - gtk_widget_queue_draw(w->toc_drawing_area); - gtk_widget_queue_draw(w->period_drawing_area); - gtk_widget_queue_draw(w->paperstrip_drawing_area); + gtk_widget_queue_draw(w->icon_drawing_area); + gtk_widget_queue_draw(w->tic_drawing_area); + gtk_widget_queue_draw(w->toc_drawing_area); + gtk_widget_queue_draw(w->period_drawing_area); + gtk_widget_queue_draw(w->paperstrip_drawing_area); #ifdef DEBUG - gtk_widget_queue_draw(w->debug_drawing_area); + gtk_widget_queue_draw(w->debug_drawing_area); #endif } /* Find the preset bph value closest corresponding to the current period */ int guess_bph(double period) { - double bph = 7200 / period; - double min = bph; - int i,ret; - - ret = 0; - for(i=0; preset_bph[i]; i++) { - double diff = fabs(bph - preset_bph[i]); - if(diff < min) { - min = diff; - ret = i; - } - } - - return preset_bph[ret]; + double bph = 7200 / period; + double min = bph; + int i,ret; + + ret = 0; + for(i=0; preset_bph[i]; i++) { + double diff = fabs(bph - preset_bph[i]); + if(diff < min) { + min = diff; + ret = i; + } + } + + return preset_bph[ret]; } /* Get data results and a flag indicating if it's current or old */ struct processing_buffers *get_data(struct main_window *w, int *old) { - struct processing_buffers *p = w->bfs; - int i; - for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); - if(i >= 0) { - // TODO: Can't we have one object kept alive instead of destroying/creating new ones every time? - if(w->old) pb_destroy_clone(w->old); // Remove the previous old data - w->old = pb_clone(&p[i]); // Store the current data as old - *old = 0; - return &p[i]; - } else { // Mark as old - *old = 1; - return w->old; - } + struct processing_buffers *p = w->bfs; + int i; + for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); + if(i >= 0) { + // TODO: Can't we have one object kept alive instead of destroying/creating new ones every time? + if(w->old) pb_destroy_clone(w->old); // Remove the previous old data + w->old = pb_clone(&p[i]); // Store the current data as old + *old = 0; + return &p[i]; + } else { // Mark as old + *old = 1; + return w->old; + } } void recompute(struct main_window *w) { - w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); - int old; - struct processing_buffers *p = get_data(w, &old); - if(old) w->signal = -w->signal; - if(p) - w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); + w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); + int old; + struct processing_buffers *p = get_data(w, &old); + if(old) w->signal = -w->signal; + if(p) + // If we have a bph set, use that for the "guess". Otherwise, calculate a guess. + w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); } -/* Called 10 times/second to keep the UI updated */ -guint refresh_window(struct main_window *w) +double get_rate(int bph, double sample_rate, struct processing_buffers *p) { - recompute(w); - redraw(w); - return TRUE; + return (7200/(bph*p->period / sample_rate) - 1)*24*3600; } -gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) +/* Calculate the amplitude from the lift angle and audio signal */ +double get_amplitude(double la, struct processing_buffers *p) { - // If you return FALSE in the "delete-event" signal handler - // GTK will emit the "destroy" signal. - return FALSE; + double ret = -1; + if(p->tic_pulse > 0 && p->toc_pulse > 0) { + double tic_amp = la * .5 / sin(M_PI * p->tic_pulse / p->period); + double toc_amp = la * .5 / sin(M_PI * p->toc_pulse / p->period); + if(la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) + ret = (tic_amp + toc_amp) / 2; + } + return ret; } -/* Draw the audio waveform */ -void draw_graph(double a, double b, cairo_t *cr, struct processing_buffers *p, GtkWidget *da) +void set_rate_label(struct main_window *w, int rate) +{ + char rate_str[20]; + char output[99]; + snprintf(rate_str, 20, "%s%d", rate > 0 ? "+" : rate < 0 ? "-" : "", abs(rate)); + snprintf(output, 99, "%4s s/d", rate_str); + gtk_label_set_markup(GTK_LABEL(w->rate_label), output); +} + +void set_beaterror_label(struct main_window *w, double be) { - int width = gtk_widget_get_allocated_width(da); - int height = gtk_widget_get_allocated_height(da); - - int n; - - int first = TRUE; - for(n=0; n<2*width; n++) { - int i = n < width ? n : 2*width - 1 - n; - double x = fmod(a + i * (b-a) / width, p->period); - if (x < 0) x += p->period; - int j = floor(x); - double y; - - if(p->waveform[j] <= 0) y = 0; - else y = p->waveform[j] * 0.4 / p->waveform_max; - - int k = round(y*height); - if (n < width) k = -k; - - if (first) { // TODO: Same code in both cases? - cairo_move_to(cr, i+.5, height/2+k+.5); - first = FALSE; - } else - cairo_line_to(cr, i+.5, height/2+k+.5); - } + char output[99]; + snprintf(output, 99, "%4.1f ms", be); + gtk_label_set_markup(GTK_LABEL(w->beaterror_label), output); +} + +void set_amplitude_label(struct main_window *w, double amp) +{ + char output[99]; + snprintf(output, 99, "%3.0f˚", amp); + gtk_label_set_markup(GTK_LABEL(w->amplitude_label), output); +} + +void set_bph_label(struct main_window *w, int bph) +{ + char output[99]; + snprintf(output, 99, "%d bph", bph); + gtk_label_set_markup(GTK_LABEL(w->bph_label), output); } #ifdef DEBUG -void draw_debug_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) +void set_fps_label(struct main_window *w, double fps) { - int width = gtk_widget_get_allocated_width(da); - int height = gtk_widget_get_allocated_height(da); - - int i; - float max = 0; - - int ai = round(a); - int bi = 1+round(b); - if(ai < 0) ai = 0; - if(bi > p->sample_count) bi = p->sample_count; - for(i=ai; idebug[i] > max) - max = p->debug[i]; - - int first = 1; - for(i=0; i= p->sample_count) j = p->sample_count-1; - - int k = round((0.1+p->debug[j]/max)*0.8*height); - - if(first) { - cairo_move_to(c,i+.5,height-k-.5); - first = 0; - } else - cairo_line_to(c,i+.5,height-k-.5); - } - } + char output[99]; + snprintf(output, 99, "%.2f fps", fps); + gtk_label_set_markup(GTK_LABEL(w->fps_label), output); } #endif -double amplitude_to_time(double lift_angle, double amp) +/* Called 10 times/second to keep the UI updated */ +guint refresh_window(struct main_window *w) { - return asin(lift_angle / (2 * amp)) / M_PI; + recompute(w); + + int old; + struct processing_buffers *p = get_data(w, &old); + + if (p) { + // Update the info labels + int bph = w->guessed_bph; + set_bph_label(w, bph); + int rate = round(get_rate(bph, w->sample_rate, p)); + set_rate_label(w, rate); + double be = fabs(p->be) * 1000 / p->sample_rate; + set_beaterror_label(w, be); + double amp = get_amplitude(w->la, p); + set_amplitude_label(w, amp); + } else { + // Set labels to "---" or leave with last data? + } + +#ifdef DEBUG + // FPS display + { + static GTimer *timer = NULL; + if (!timer) timer = g_timer_new(); + else { + double fps = 1./g_timer_elapsed(timer, NULL); + set_fps_label(w, fps); + g_timer_reset(timer); + } + } +#endif + + redraw(w); // Redraw the DrawingArea widgets (waveforms etc.) + + return TRUE; } -/* Draw the watch icon at the start of the info area */ -double draw_watch_icon(cairo_t *c, int signal) +gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { - int happy = !!signal; - cairo_set_line_width(c,3); - cairo_set_source(c, happy ? icon1 : icon2); - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT * 0.35, OUTPUT_WINDOW_HEIGHT * (0.65 - 0.3*happy)); - cairo_stroke(c); - cairo_arc(c, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.4, 0, 2*M_PI); - cairo_stroke(c); - const int l = OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1); - int i; - cairo_set_line_width(c,1); - for(i = 0; i < signal; i++) { - cairo_move_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(c, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_stroke_preserve(c); - cairo_fill(c); - } - return OUTPUT_WINDOW_HEIGHT + 3*l; + // If you return FALSE in the "delete-event" signal handler + // GTK will emit the "destroy" signal. + return FALSE; } -double get_rate(int bph, double sample_rate, struct processing_buffers *p) +/* Draw the audio waveform */ +void draw_graph(double a, double b, cairo_t *cr, struct processing_buffers *p, GtkWidget *da) { - return (7200/(bph*p->period / sample_rate) - 1)*24*3600; + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + int n; + + int first = TRUE; + for(n=0; n<2*width; n++) { + int i = n < width ? n : 2*width - 1 - n; + double x = fmod(a + i * (b-a) / width, p->period); + if (x < 0) x += p->period; + int j = floor(x); + double y; + + if(p->waveform[j] <= 0) y = 0; + else y = p->waveform[j] * 0.4 / p->waveform_max; + + int k = round(y*height); + if (n < width) k = -k; + + if (first) { + cairo_move_to(cr, i+.5, height/2+k+.5); + first = FALSE; + } else + cairo_line_to(cr, i+.5, height/2+k+.5); + } } -/* Calculate the amplitude from the lift angle and audio signal */ -double get_amplitude(double la, struct processing_buffers *p) +#ifdef DEBUG +void draw_debug_graph(double a, double b, cairo_t *c, struct processing_buffers *p, GtkWidget *da) { - double ret = -1; - if(p->tic_pulse > 0 && p->toc_pulse > 0) { - double tic_amp = la * .5 / sin(M_PI * p->tic_pulse / p->period); - double toc_amp = la * .5 / sin(M_PI * p->toc_pulse / p->period); - if(la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) - ret = (tic_amp + toc_amp) / 2; - } - return ret; + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + int i; + float max = 0; + + int ai = round(a); + int bi = 1+round(b); + if(ai < 0) ai = 0; + if(bi > p->sample_count) bi = p->sample_count; + for(i=ai; idebug[i] > max) + max = p->debug[i]; + + int first = 1; + for(i=0; i= p->sample_count) j = p->sample_count-1; + + int k = round((0.1+p->debug[j]/max)*0.8*height); + + if(first) { + cairo_move_to(c,i+.5,height-k-.5); + first = 0; + } else + cairo_line_to(c,i+.5,height-k-.5); + } + } +} +#endif + +double amplitude_to_time(double lift_angle, double amp) +{ + return asin(lift_angle / (2 * amp)) / M_PI; } /* Set up default line width and background color before each drawing operation */ void cairo_init(cairo_t *c) { - cairo_set_line_width(c, 1); - - cairo_set_source(c, bg_color); - cairo_paint(c); + cairo_set_line_width(c, 1); + + cairo_set_source(c, bg_color); + cairo_paint(c); } double print_number(cairo_t *c, double x, double y, char *s) { - cairo_text_extents_t extents; - cairo_text_extents(c,"0",&extents); - double z = extents.x_advance; - char t[2]; - t[1] = 0; - while((t[0] = *s++)) { - cairo_text_extents(c,t,&extents); - cairo_move_to(c, x + (z - extents.x_advance) / 2, y); - cairo_show_text(c,t); - x += z; - } - return x; + cairo_text_extents_t extents; + cairo_text_extents(c,"0",&extents); + double z = extents.x_advance; + char t[2]; + t[1] = 0; + while((t[0] = *s++)) { + cairo_text_extents(c,t,&extents); + cairo_move_to(c, x + (z - extents.x_advance) / 2, y); + cairo_show_text(c,t); + x += z; + } + return x; } /* Draws either the tic or toc waveform & grid in their respective widget */ void draw_waveform( - struct main_window *w, - GtkWidget *da, - cairo_t *cr, - int (*get_offset)(struct processing_buffers*), - double (*get_pulse)(struct processing_buffers*) ) + struct main_window *w, + GtkWidget *da, + cairo_t *cr, + int (*get_offset)(struct processing_buffers*), + double (*get_pulse)(struct processing_buffers*) ) { - cairo_init(cr); - - int width = gtk_widget_get_allocated_width(da); - int height = gtk_widget_get_allocated_height(da); - - // Calculate font size for the amplitude and timing text on the grid - int fontsize = gtk_widget_get_allocated_width(w->window) / 90; // TODO: Better sizing. Just keep it at 12? - if (fontsize < 12) - fontsize = 12; - cairo_set_font_size(cr, fontsize); - - int i; - - // Draw vertical time lines every ms - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { - int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); - cairo_move_to(cr, x + .5, height / 2 + .5); - cairo_line_to(cr, x + .5, height - .5); - if(i%5) - cairo_set_source(cr, grid_color); - else - cairo_set_source(cr, grid2_color); - cairo_stroke(cr); - } - - // Draw numbers for time scale, every 5 ms - cairo_set_source(cr, text); - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { - if(!(i%5)) { - int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); - char s[10]; - snprintf(s, 10, "%d", i); - cairo_move_to(cr, x+fontsize/4, height-fontsize/2); - cairo_show_text(cr, s); - } - } - - cairo_text_extents_t extents; - - // Draw "ms" label - cairo_text_extents(cr, "ms", &extents); - cairo_move_to(cr, width - extents.x_advance - fontsize/4, height-fontsize/2); - cairo_show_text(cr, "ms"); - - int old; // Flag set if the data isn't current - struct processing_buffers *p = get_data(w, &old); - double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; - - // Draw vertical amplitude lines - for (i = 10; i < 360; i+=10) { - if (2*i < w->la) continue; - double t = period*amplitude_to_time(w->la, i); - if (t > .001 * NEGATIVE_SPAN) continue; - int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); - cairo_move_to(cr, x+.5, .5); - cairo_line_to(cr, x+.5, height / 2 + .5); - if (i % 50) - cairo_set_source(cr, grid_color); - else - cairo_set_source(cr, grid2_color); - cairo_stroke(cr); - } - - // Draw numbers for amplitude scale - double last_x = 0; - cairo_set_source(cr, text); - for(i = 50; i < 360; i+=50) { - double t = period*amplitude_to_time(w->la, i); - if(t > .001 * NEGATIVE_SPAN) continue; - int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); - if(x > last_x) { - char s[10]; - snprintf(s, 10, "%d", abs(i)); - cairo_move_to(cr, x + fontsize/4, fontsize * 3 / 2); - cairo_show_text(cr, s); - cairo_text_extents(cr, s, &extents); - last_x = x + fontsize/4 + extents.x_advance; - } - } - - // Draw "deg" label - cairo_text_extents(cr, "deg", &extents); - cairo_move_to(cr,width - extents.x_advance - fontsize/4, fontsize * 3 / 2); - cairo_show_text(cr,"deg"); - - // Draw audio waveform - if (p) { - double span = 0.001 * w->sample_rate; - int offset = get_offset(p); - - double a = offset - span * NEGATIVE_SPAN; - double b = offset + span * POSITIVE_SPAN; - - draw_graph(a,b,cr,p,da); - - // Make the audio waveform yellow if it's not current. - cairo_set_source(cr, old ? stopped_color : waveform_color); - cairo_stroke_preserve(cr); - cairo_fill(cr); - - double pulse = get_pulse(p); - if (pulse > 0) { - // Draw vertical blue line at start of pulse - int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); - cairo_move_to(cr, x, 1); - cairo_line_to(cr, x, height - 1); - cairo_set_source(cr, pulse_color); - cairo_set_line_width(cr, 2); - cairo_stroke(cr); - } - } else { // If no data, just draw the center line - cairo_move_to(cr, .5, height / 2 + .5); - cairo_line_to(cr, width - .5, height / 2 + .5); - cairo_set_source(cr, stopped_color); - cairo_stroke(cr); - } - + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(da); + int height = gtk_widget_get_allocated_height(da); + + // Calculate font size for the amplitude and timing text on the grid + int fontsize = gtk_widget_get_allocated_width(w->window) / 90; // TODO: Better sizing. Just keep it at 12? + if (fontsize < 12) + fontsize = 12; + cairo_set_font_size(cr, fontsize); + + int i; + + // Draw vertical time lines every ms + for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); + cairo_move_to(cr, x + .5, height / 2 + .5); + cairo_line_to(cr, x + .5, height - .5); + if(i%5) + cairo_set_source(cr, grid_color); + else + cairo_set_source(cr, grid2_color); + cairo_stroke(cr); + } + + // Draw numbers for time scale, every 5 ms + cairo_set_source(cr, text); + for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + if(!(i%5)) { + int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); + char s[10]; + snprintf(s, 10, "%d", i); + cairo_move_to(cr, x+fontsize/4, height-fontsize/2); + cairo_show_text(cr, s); + } + } + + cairo_text_extents_t extents; + + // Draw "ms" label + cairo_text_extents(cr, "ms", &extents); + cairo_move_to(cr, width - extents.x_advance - fontsize/4, height-fontsize/2); + cairo_show_text(cr, "ms"); + + int old; // Flag set if the data isn't current + struct processing_buffers *p = get_data(w, &old); + double period = p ? p->period / w->sample_rate : 7200. / w->guessed_bph; + + // Draw vertical amplitude lines + for (i = 10; i < 360; i+=10) { + if (2*i < w->la) continue; + double t = period*amplitude_to_time(w->la, i); + if (t > .001 * NEGATIVE_SPAN) continue; + int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); + cairo_move_to(cr, x+.5, .5); + cairo_line_to(cr, x+.5, height / 2 + .5); + if (i % 50) + cairo_set_source(cr, grid_color); + else + cairo_set_source(cr, grid2_color); + cairo_stroke(cr); + } + + // Draw numbers for amplitude scale + double last_x = 0; + cairo_set_source(cr, text); + for(i = 50; i < 360; i+=50) { + double t = period*amplitude_to_time(w->la, i); + if(t > .001 * NEGATIVE_SPAN) continue; + int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); + if(x > last_x) { + char s[10]; + snprintf(s, 10, "%d", abs(i)); + cairo_move_to(cr, x + fontsize/4, fontsize * 3 / 2); + cairo_show_text(cr, s); + cairo_text_extents(cr, s, &extents); + last_x = x + fontsize/4 + extents.x_advance; + } + } + + // Draw "deg" label + cairo_text_extents(cr, "deg", &extents); + cairo_move_to(cr,width - extents.x_advance - fontsize/4, fontsize * 3 / 2); + cairo_show_text(cr,"deg"); + + // Draw audio waveform + if (p) { + double span = 0.001 * w->sample_rate; + int offset = get_offset(p); + + double a = offset - span * NEGATIVE_SPAN; + double b = offset + span * POSITIVE_SPAN; + + draw_graph(a,b,cr,p,da); + + // Make the audio waveform yellow if it's not current. + cairo_set_source(cr, old ? stopped_color : waveform_color); + cairo_stroke_preserve(cr); + cairo_fill(cr); + + double pulse = get_pulse(p); + if (pulse > 0) { + // Draw vertical blue line at start of pulse + int x = round((NEGATIVE_SPAN - pulse * 1000 / p->sample_rate) * width / (POSITIVE_SPAN + NEGATIVE_SPAN)); + cairo_move_to(cr, x, 1); + cairo_line_to(cr, x, height - 1); + cairo_set_source(cr, pulse_color); + cairo_set_line_width(cr, 2); + cairo_stroke(cr); + } + } else { // If no data, just draw the center line + cairo_move_to(cr, .5, height / 2 + .5); + cairo_line_to(cr, width - .5, height / 2 + .5); + cairo_set_source(cr, stopped_color); + cairo_stroke(cr); + } + } /* Used as a function parameter in draw_waveform() */ int get_tic(struct processing_buffers *p) { - return p->tic; + return p->tic; } /* Used as a function parameter in draw_waveform() */ int get_toc(struct processing_buffers *p) { - return p->toc; + return p->toc; } /* Used as a function parameter in draw_waveform() */ double get_tic_pulse(struct processing_buffers *p) { - return p->tic_pulse; + return p->tic_pulse; } /* Used as a function parameter in draw_waveform() */ double get_toc_pulse(struct processing_buffers *p) { - return p->toc_pulse; + return p->toc_pulse; } -gboolean info_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) +/* Draw the watch icon in the info area */ +gboolean icon_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - cairo_init(cr); - - int old; - struct processing_buffers *p = get_data(w, &old); - - double x = draw_watch_icon(cr, w->signal); - - char outputs[8][20]; - - if(p) { - int bph = w->guessed_bph; - int rate = round(get_rate(bph, w->sample_rate, p)); - double be = fabs(p->be) * 1000 / p->sample_rate; - double amp = get_amplitude(w->la, p); - char rates[20]; - snprintf(rates, 20, "%s%d", rate > 0 ? "+" : rate < 0 ? "-" : "", abs(rate)); - snprintf(outputs[0], 20, "%4s",rates); - snprintf(outputs[2], 20, " %4.1f",be); - if(amp > 0) - snprintf(outputs[4], 20, " %3.0f",amp); - else - strcpy(outputs[4]," ---"); - } else { - strcpy(outputs[0],"----"); - strcpy(outputs[2]," ----"); - strcpy(outputs[4]," ---"); - } - snprintf(outputs[6], 20, " %d",w->guessed_bph); - - strcpy(outputs[1]," s/d"); - strcpy(outputs[3]," ms"); - strcpy(outputs[5]," deg"); - strcpy(outputs[7]," bph"); - - cairo_text_extents_t extents; - - cairo_set_font_size(cr, OUTPUT_FONT); - cairo_text_extents(cr,"0", &extents); - double y = (double)OUTPUT_WINDOW_HEIGHT/2 - extents.y_bearing - extents.height/2; - - int i; - for(i=0; i <8; i++) { - if(i%2) { - cairo_set_source(cr, text); - cairo_move_to(cr,x,y); - cairo_set_font_size(cr, OUTPUT_FONT*2/3); - cairo_show_text(cr,outputs[i]); - cairo_text_extents(cr,outputs[i],&extents); - x += extents.x_advance; - } else { - cairo_set_source(cr, i > 4 || !p || !old ? text : stopped_color); - cairo_set_font_size(cr, OUTPUT_FONT); - x = print_number(cr,x,y,outputs[i]); - } - } -#ifdef DEBUG - { - static GTimer *timer = NULL; - if (!timer) timer = g_timer_new(); - else { - char s[50]; - snprintf(s, 50, " %.2f fps",1./g_timer_elapsed(timer, NULL)); - cairo_set_source(cr, text); - cairo_set_font_size(cr, OUTPUT_FONT); - cairo_move_to(cr,x,y); - cairo_show_text(cr,s); - g_timer_reset(timer); - } - } -#endif - - return FALSE; + int happy = !!w->signal; + + // Watch hands + cairo_set_line_width(cr, 5); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); // Rounded hands + cairo_set_source(cr, happy ? icon1 : icon2); + cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); + cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT * 0.35, OUTPUT_WINDOW_HEIGHT * (0.65 - 0.3*happy)); + cairo_stroke(cr); + + // Watch outline + cairo_set_line_width(cr, 6); + cairo_arc(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.4, 0, 2*M_PI); + cairo_stroke(cr); + + const int l = OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1); + int i; + cairo_set_line_width(cr, 1); + for(i = 0; i < w->signal; i++) { + cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); + cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + cairo_stroke_preserve(cr); + cairo_fill(cr); + } + + return FALSE; } gboolean tic_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - draw_waveform(w, w->tic_drawing_area, cr, get_tic, get_tic_pulse); - return FALSE; + draw_waveform(w, w->tic_drawing_area, cr, get_tic, get_tic_pulse); + return FALSE; } gboolean toc_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - draw_waveform(w, w->toc_drawing_area, cr, get_toc, get_toc_pulse); - return FALSE; + draw_waveform(w, w->toc_drawing_area, cr, get_toc, get_toc_pulse); + return FALSE; } gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - cairo_init(cr); - - int width = gtk_widget_get_allocated_width(w->period_drawing_area); - int height = gtk_widget_get_allocated_height(w->period_drawing_area); - - int old; - struct processing_buffers *p = get_data(w, &old); - - double toc, a=0, b=0; - - if(p) { - toc = p->tic < p->toc ? p->toc : p->toc + p->period; - a = ((double)p->tic + toc)/2 - p->period/2; - b = ((double)p->tic + toc)/2 + p->period/2; - - cairo_move_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_line_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(cr, range_color); - cairo_fill(cr); - - cairo_move_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_line_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); - cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); - cairo_set_source(cr, range_color); - cairo_fill(cr); - } - - int i; - for(i = 1; i < 16; i++) { - int x = i * width / 16; - cairo_move_to(cr, x+.5, .5); - cairo_line_to(cr, x+.5, height - .5); - if(i % 4) - cairo_set_source(cr, grid_color); - else - cairo_set_source(cr, grid2_color); - cairo_stroke(cr); - } - - if(p) { - draw_graph(a,b,cr,p,w->period_drawing_area); - - cairo_set_source(cr, old ? stopped_color : waveform_color); - cairo_stroke_preserve(cr); - cairo_fill(cr); - } else { - cairo_move_to(cr, .5, height / 2 + .5); - cairo_line_to(cr, width - .5, height / 2 + .5); - cairo_set_source(cr, stopped_color); - cairo_stroke(cr); - } - - return FALSE; + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(w->period_drawing_area); + int height = gtk_widget_get_allocated_height(w->period_drawing_area); + + int old; + struct processing_buffers *p = get_data(w, &old); + + double toc, a=0, b=0; + + if(p) { + toc = p->tic < p->toc ? p->toc : p->toc + p->period; + a = ((double)p->tic + toc)/2 - p->period/2; + b = ((double)p->tic + toc)/2 + p->period/2; + + cairo_move_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_line_to(cr, (p->tic - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (p->tic - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_set_source(cr, range_color); + cairo_fill(cr); + + cairo_move_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_line_to(cr, (toc - a - NEGATIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, height); + cairo_line_to(cr, (toc - a + POSITIVE_SPAN*.001*w->sample_rate) * width/p->period, 0); + cairo_set_source(cr, range_color); + cairo_fill(cr); + } + + int i; + for(i = 1; i < 16; i++) { + int x = i * width / 16; + cairo_move_to(cr, x+.5, .5); + cairo_line_to(cr, x+.5, height - .5); + if(i % 4) + cairo_set_source(cr, grid_color); + else + cairo_set_source(cr, grid2_color); + cairo_stroke(cr); + } + + if(p) { + draw_graph(a,b,cr,p,w->period_drawing_area); + + cairo_set_source(cr, old ? stopped_color : waveform_color); + cairo_stroke_preserve(cr); + cairo_fill(cr); + } else { + cairo_move_to(cr, .5, height / 2 + .5); + cairo_line_to(cr, width - .5, height / 2 + .5); + cairo_set_source(cr, stopped_color); + cairo_stroke(cr); + } + + return FALSE; } extern volatile uint64_t timestamp; gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - int i,old; - struct processing_buffers *p = get_data(w, &old); + int i,old; + struct processing_buffers *p = get_data(w, &old); #ifdef LIGHT - uint64_t time = timestamp / 2; + uint64_t time = timestamp / 2; #else - uint64_t time = timestamp; + uint64_t time = timestamp; #endif - if(p && !old) { - uint64_t last = w->events[w->events_wp]; - for(i=0; ievents[i]; i++) - if(p->events[i] > last + floor(p->period / 4)) { - if(++w->events_wp == EVENTS_COUNT) w->events_wp = 0; - w->events[w->events_wp] = p->events[i]; - debug("event at %llu\n", w->events[w->events_wp]); - } - w->events_from = p->timestamp - ceil(p->period); - } else { - w->events_from = time; - } - - cairo_init(cr); - - int width = gtk_widget_get_allocated_width(w->paperstrip_drawing_area); - int height = gtk_widget_get_allocated_height(w->paperstrip_drawing_area); - - int stopped = 0; - if(w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { - time = 5 * w->sample_rate + w->events[w->events_wp]; - stopped = 1; - } - - int strip_width = round(width / (1 + PAPERSTRIP_MARGIN)); - - // Draw the 4 directional lines in the paperstrip - cairo_set_line_width(cr, 1.3); - - if (p && w->events[w->events_wp]) { - double rate = get_rate(w->guessed_bph, w->sample_rate, p); - double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); - if (slope <= 1 && slope >= -1) { - for (i=0; i<4; i++) { - double y = 0; - cairo_move_to(cr, (double)width * (i+.5) / 4, 0); - for(;;) { - double x = y * slope + (double)width * (i+.5) / 4; - x = fmod(x, width); - if (x < 0) x += width; - double nx = x + slope * (height - y); - if (nx >= 0 && nx <= width) { - cairo_line_to(cr, nx, height); - break; - } else { - double d = slope > 0 ? width - x : x; - y += d / fabs(slope); - cairo_line_to(cr, slope > 0 ? width : 0, y); - y += 1; - if (y > height) break; - cairo_move_to(cr, slope > 0 ? 0 : width, y); - } - } - } - cairo_set_source(cr, pulse_color); - cairo_stroke(cr); - } - } - - cairo_set_line_width(cr, 1); - - int left_margin = (width - strip_width) / 2; - int right_margin = (width + strip_width) / 2; - - // Draw the 2 vertical lines at the margins of the paperstrip - cairo_move_to(cr, left_margin + .5, .5); - cairo_line_to(cr, left_margin + .5, height - .5); - cairo_move_to(cr, right_margin + .5, .5); - cairo_line_to(cr, right_margin + .5, height - .5); - cairo_set_source(cr, grid_color); - cairo_stroke(cr); - - // Draw the horizontal lines of the paperstrip - double sweep = w->sample_rate * 3600. / w->guessed_bph; - double now = sweep*ceil(time/sweep); - double ten_s = w->sample_rate * 10 / sweep; - double last_line = fmod(now/sweep, ten_s); - int last_tenth = floor(now/(sweep*ten_s)); - for(i=0;;i++) { - double y = 0.5 + round(last_line + i*ten_s); - if(y > height) break; - cairo_move_to(cr, .5, y); - cairo_line_to(cr, width-.5, y); - cairo_set_source(cr, (last_tenth-i)%6 ? grid_color : grid2_color); - cairo_stroke(cr); - } - - // Plot the tick/tocks on the paperstrip - cairo_set_source(cr, stopped ? stopped_color : waveform_color); - for(i = w->events_wp;;) { - if(!w->events[i]) break; - double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); - int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); - int row = floor(event / sweep); - if(row >= height) break; - cairo_move_to(cr,column,row); - cairo_line_to(cr,column+1,row); - cairo_line_to(cr,column+1,row+1); - cairo_line_to(cr,column,row+1); - cairo_line_to(cr,column,row); - cairo_fill(cr); - if(column < width - strip_width && row > 0) { - column += strip_width; - row -= 1; - cairo_move_to(cr,column,row); - cairo_line_to(cr,column+1,row); - cairo_line_to(cr,column+1,row+1); - cairo_line_to(cr,column,row+1); - cairo_line_to(cr,column,row); - cairo_fill(cr); - } - if(--i < 0) i = EVENTS_COUNT - 1; - if(i == w->events_wp) break; - } - - // Draw the arrowed line for the ms scale at the bottom - cairo_set_source(cr, text); - cairo_set_line_width(cr, 2); - cairo_move_to(cr, left_margin + 3, height - 20.5); - cairo_line_to(cr, right_margin - 3, height - 20.5); - cairo_stroke(cr); - cairo_set_line_width(cr,1); - cairo_move_to(cr, left_margin + .5, height - 20.5); - cairo_line_to(cr, left_margin + 5.5, height - 15.5); - cairo_line_to(cr, left_margin + 5.5, height - 25.5); - cairo_line_to(cr, left_margin + .5, height - 20.5); - cairo_fill(cr); - cairo_move_to(cr, right_margin + .5, height - 20.5); - cairo_line_to(cr, right_margin - 4.5, height - 15.5); - cairo_line_to(cr, right_margin - 4.5, height - 25.5); - cairo_line_to(cr, right_margin + .5, height - 20.5); - cairo_fill(cr); - - // Draw the ms scale at the bottom of the paperstrip - int fontsize = gtk_widget_get_allocated_width(w->window) / 90; - if(fontsize < 12) - fontsize = 12; - cairo_set_font_size(cr, fontsize); - - char s[50]; - cairo_text_extents_t extents; - - snprintf(s, 50, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); - cairo_text_extents(cr,s,&extents); - cairo_move_to(cr, (width - extents.x_advance)/2, height - 30); - cairo_show_text(cr, s); - - return FALSE; + if(p && !old) { + uint64_t last = w->events[w->events_wp]; + for(i=0; ievents[i]; i++) + if(p->events[i] > last + floor(p->period / 4)) { + if(++w->events_wp == EVENTS_COUNT) w->events_wp = 0; + w->events[w->events_wp] = p->events[i]; + debug("event at %llu\n", w->events[w->events_wp]); + } + w->events_from = p->timestamp - ceil(p->period); + } else { + w->events_from = time; + } + + cairo_init(cr); + + int width = gtk_widget_get_allocated_width(w->paperstrip_drawing_area); + int height = gtk_widget_get_allocated_height(w->paperstrip_drawing_area); + + int stopped = 0; + if(w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { + time = 5 * w->sample_rate + w->events[w->events_wp]; + stopped = 1; + } + + int strip_width = round(width / (1 + PAPERSTRIP_MARGIN)); + + // Draw the 4 directional lines in the paperstrip + cairo_set_line_width(cr, 1.3); + + if (p && w->events[w->events_wp]) { + double rate = get_rate(w->guessed_bph, w->sample_rate, p); + double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); + if (slope <= 1 && slope >= -1) { + for (i=0; i<4; i++) { + double y = 0; + cairo_move_to(cr, (double)width * (i+.5) / 4, 0); + for(;;) { + double x = y * slope + (double)width * (i+.5) / 4; + x = fmod(x, width); + if (x < 0) x += width; + double nx = x + slope * (height - y); + if (nx >= 0 && nx <= width) { + cairo_line_to(cr, nx, height); + break; + } else { + double d = slope > 0 ? width - x : x; + y += d / fabs(slope); + cairo_line_to(cr, slope > 0 ? width : 0, y); + y += 1; + if (y > height) break; + cairo_move_to(cr, slope > 0 ? 0 : width, y); + } + } + } + cairo_set_source(cr, pulse_color); + cairo_stroke(cr); + } + } + + cairo_set_line_width(cr, 1); + + int left_margin = (width - strip_width) / 2; + int right_margin = (width + strip_width) / 2; + + // Draw the 2 vertical lines at the margins of the paperstrip + cairo_move_to(cr, left_margin + .5, .5); + cairo_line_to(cr, left_margin + .5, height - .5); + cairo_move_to(cr, right_margin + .5, .5); + cairo_line_to(cr, right_margin + .5, height - .5); + cairo_set_source(cr, grid_color); + cairo_stroke(cr); + + // Draw the horizontal lines of the paperstrip + double sweep = w->sample_rate * 3600. / w->guessed_bph; + double now = sweep*ceil(time/sweep); + double ten_s = w->sample_rate * 10 / sweep; + double last_line = fmod(now/sweep, ten_s); + int last_tenth = floor(now/(sweep*ten_s)); + for(i=0;;i++) { + double y = 0.5 + round(last_line + i*ten_s); + if(y > height) break; + cairo_move_to(cr, .5, y); + cairo_line_to(cr, width-.5, y); + cairo_set_source(cr, (last_tenth-i)%6 ? grid_color : grid2_color); + cairo_stroke(cr); + } + + // Plot the tick/tocks on the paperstrip + cairo_set_source(cr, stopped ? stopped_color : waveform_color); + for(i = w->events_wp;;) { + if(!w->events[i]) break; + double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); + int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); + int row = floor(event / sweep); + if(row >= height) break; + cairo_move_to(cr,column,row); + cairo_line_to(cr,column+1,row); + cairo_line_to(cr,column+1,row+1); + cairo_line_to(cr,column,row+1); + cairo_line_to(cr,column,row); + cairo_fill(cr); + if(column < width - strip_width && row > 0) { + column += strip_width; + row -= 1; + cairo_move_to(cr,column,row); + cairo_line_to(cr,column+1,row); + cairo_line_to(cr,column+1,row+1); + cairo_line_to(cr,column,row+1); + cairo_line_to(cr,column,row); + cairo_fill(cr); + } + if(--i < 0) i = EVENTS_COUNT - 1; + if(i == w->events_wp) break; + } + + // Draw the arrowed line for the ms scale at the bottom + cairo_set_source(cr, text); + cairo_set_line_width(cr, 2); + cairo_move_to(cr, left_margin + 3, height - 20.5); + cairo_line_to(cr, right_margin - 3, height - 20.5); + cairo_stroke(cr); + cairo_set_line_width(cr,1); + cairo_move_to(cr, left_margin + .5, height - 20.5); + cairo_line_to(cr, left_margin + 5.5, height - 15.5); + cairo_line_to(cr, left_margin + 5.5, height - 25.5); + cairo_line_to(cr, left_margin + .5, height - 20.5); + cairo_fill(cr); + cairo_move_to(cr, right_margin + .5, height - 20.5); + cairo_line_to(cr, right_margin - 4.5, height - 15.5); + cairo_line_to(cr, right_margin - 4.5, height - 25.5); + cairo_line_to(cr, right_margin + .5, height - 20.5); + cairo_fill(cr); + + // Draw the ms scale at the bottom of the paperstrip + int fontsize = gtk_widget_get_allocated_width(w->window) / 90; + if(fontsize < 12) + fontsize = 12; + cairo_set_font_size(cr, fontsize); + + char s[50]; + cairo_text_extents_t extents; + + snprintf(s, 50, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); + cairo_text_extents(cr,s,&extents); + cairo_move_to(cr, (width - extents.x_advance)/2, height - 30); + cairo_show_text(cr, s); + + return FALSE; } #ifdef DEBUG gboolean debug_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { - int old = 0; - struct processing_buffers *p = get_data(w, &old); - - if(p) { - double a = p->period / 10; - double b = p->period * 2; - - cairo_init(cr); - - draw_debug_graph(a,b,cr,p,w->debug_drawing_area); - - cairo_set_source(cr, stopped ? stopped_color : waveform_color); - cairo_stroke(cr); - } - - return FALSE; + cairo_init(cr); + + int old = 0; + struct processing_buffers *p = get_data(w, &old); + + if(p) { + double a = p->period / 10; + double b = p->period * 2; + + draw_debug_graph(a,b,cr,p,w->debug_drawing_area); + + cairo_set_source(cr, old ? stopped_color : waveform_color); + cairo_stroke(cr); + } + + return FALSE; } #endif /* Called when the user changes the bph box value */ void handle_bph_change(GtkComboBox *b, struct main_window *w) { - char *s = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(b)); - if(s) { - int n; - char *t; - n = (int)strtol(s,&t,10); - if(*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; - else w->bph = w->guessed_bph = n; - g_free(s); - recompute(w); - redraw(w); - } + char *s = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(b)); + if(s) { + int n; + char *t; + n = (int)strtol(s, &t, 10); + if(*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; + else w->bph = w->guessed_bph = n; + g_free(s); + recompute(w); + set_bph_label(w, w->guessed_bph); + redraw(w); + } } /* Called when the user changes the lift angle value */ void handle_la_change(GtkSpinButton *b, struct main_window *w) { - double la = gtk_spin_button_get_value(b); - if(la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; - w->la = la; - redraw(w); + double la = gtk_spin_button_get_value(b); + if(la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; + w->la = la; + redraw(w); } /* Called when the user clicks the Clear button */ void handle_clear_trace(GtkButton *b, struct main_window *w) { - memset(w->events, 0, EVENTS_COUNT*sizeof(uint64_t)); - redraw(w); + memset(w->events, 0, EVENTS_COUNT*sizeof(uint64_t)); + redraw(w); } /* Called when the user clicks the Center button */ void handle_center_trace(GtkButton *b, struct main_window *w) { - uint64_t last_ev = w->events[w->events_wp]; - if(last_ev) { - double sweep = w->sample_rate * 3600. / (PAPERSTRIP_ZOOM * w->guessed_bph); - w->trace_centering = fmod(last_ev + .5*sweep , sweep); - } else - w->trace_centering = 0; - redraw(w); + uint64_t last_ev = w->events[w->events_wp]; + if(last_ev) { + double sweep = w->sample_rate * 3600. / (PAPERSTRIP_ZOOM * w->guessed_bph); + w->trace_centering = fmod(last_ev + .5*sweep , sweep); + } else + w->trace_centering = 0; + redraw(w); } void quit() { - gtk_main_quit(); + gtk_main_quit(); } /* Set up the main window and populate with widgets */ void init_main_window(struct main_window *w) { - w->signal = 0; - - w->events = malloc(EVENTS_COUNT * sizeof(uint64_t)); - memset(w->events,0,EVENTS_COUNT * sizeof(uint64_t)); - w->events_wp = 0; - w->events_from = 0; - w->trace_centering = 0; - - w->guessed_bph = w->last_bph = DEFAULT_BPH; - w->bph = 0; - w->la = DEFAULT_LA; - - gtk_container_set_border_width(GTK_CONTAINER(w->window), 5); // Border around the window - g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); // Signal emitted if a user requests that a toplevel window is closed. - g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); - - gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); - - // Populate the settings grid - GtkWidget *settings_grid = gtk_grid_new(); // The grid containing the settings, default to horizontal orientation - gtk_grid_set_column_spacing(GTK_GRID(settings_grid), 2); - - // Beat mode Label - GtkWidget *bph_label = gtk_label_new("Beat mode"); - gtk_container_add (GTK_CONTAINER(settings_grid), bph_label); // Add to grid - - // BPH combo box - w->bph_combo_box = gtk_combo_box_text_new_with_entry(); - // Fill in pre-defined values - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "Auto"); - int *bph; - for(bph = preset_bph; *bph; bph++) { - char s[50]; - snprintf(s, 50, "%d", *bph); - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); - } - gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); - gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus. Not working.... - g_signal_connect (w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); - gtk_container_add (GTK_CONTAINER(settings_grid), w->bph_combo_box); - - // Lift angle label - GtkWidget *la_label = gtk_label_new("Lift angle"); - gtk_widget_set_margin_start(la_label, 10); // Make space from the widget in front - gtk_container_add (GTK_CONTAINER(settings_grid), la_label); - - // Lift angle spin button - w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); // Start at default value - g_signal_connect (w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); - gtk_container_add (GTK_CONTAINER(settings_grid), w->la_spin_button); - + w->signal = 0; + + w->events = malloc(EVENTS_COUNT * sizeof(uint64_t)); + memset(w->events,0,EVENTS_COUNT * sizeof(uint64_t)); + w->events_wp = 0; + w->events_from = 0; + w->trace_centering = 0; + + w->guessed_bph = w->last_bph = DEFAULT_BPH; + w->bph = 0; + w->la = DEFAULT_LA; + + gtk_container_set_border_width(GTK_CONTAINER(w->window), 5); // Border around the window + g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); // Signal emitted if a user requests that a toplevel window is closed. + g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); + + gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); + + // Populate the settings grid + GtkWidget *settings_grid = gtk_grid_new(); // The grid containing the settings, default to horizontal orientation + gtk_grid_set_column_spacing(GTK_GRID(settings_grid), 2); + + // Beat mode Label + GtkWidget *bphmode_label = gtk_label_new("Beat mode"); + gtk_container_add(GTK_CONTAINER(settings_grid), bphmode_label); // Add to grid + + // BPH combo box + w->bph_combo_box = gtk_combo_box_text_new_with_entry(); + gtk_widget_set_size_request(w->bph_combo_box, 40, -1); // Try making the dropdown less wide. Not working... + // Fill in pre-defined values + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "Automatic"); + int *bph; + for(bph = preset_bph; *bph; bph++) { + char s[50]; + snprintf(s, 50, "%d", *bph); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); + gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus. Not working.... + g_signal_connect(w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); + gtk_container_add(GTK_CONTAINER(settings_grid), w->bph_combo_box); + + // Lift angle label + GtkWidget *la_label = gtk_label_new("Lift angle"); + gtk_widget_set_margin_start(la_label, 10); // Make space from the widget in front + gtk_container_add(GTK_CONTAINER(settings_grid), la_label); + + // Lift angle spin button + w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); // Start at default value + g_signal_connect(w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); + gtk_container_add(GTK_CONTAINER(settings_grid), w->la_spin_button); + + // Info grid + GtkWidget *info_grid = gtk_grid_new(); // The grid containing the info text, default to horizontal orientation + gtk_grid_set_column_spacing(GTK_GRID(info_grid), 20); + + // Watch icon + w->icon_drawing_area = gtk_drawing_area_new(); + int width = OUTPUT_WINDOW_HEIGHT + 3*(OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1)); + gtk_widget_set_size_request(w->icon_drawing_area, width, OUTPUT_WINDOW_HEIGHT); + g_signal_connect(w->icon_drawing_area, "draw", G_CALLBACK(icon_draw_event), w); + gtk_container_add(GTK_CONTAINER(info_grid), w->icon_drawing_area); // Add to grid + + // Rate Label + w->rate_label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(w->rate_label), "---- s/d"); + gtk_widget_set_name(w->rate_label, "rate"); + gtk_container_add(GTK_CONTAINER(info_grid), w->rate_label); // Add to grid + + // Beat Error Label + w->beaterror_label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(w->beaterror_label), "---- ms"); + gtk_widget_set_name(w->beaterror_label, "beaterror"); + gtk_container_add(GTK_CONTAINER(info_grid), w->beaterror_label); // Add to grid + + // Amplitude Label + w->amplitude_label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(w->amplitude_label), "---˚"); + gtk_widget_set_name(w->amplitude_label, "amplitude"); + gtk_container_add(GTK_CONTAINER(info_grid), w->amplitude_label); // Add to grid + + // BPH Label + w->bph_label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(w->bph_label), "21600 bph"); + gtk_widget_set_name(w->bph_label, "bph"); + gtk_container_add(GTK_CONTAINER(info_grid), w->bph_label); // Add to grid + + // Populate the panes + GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + + GtkWidget *left_grid = gtk_grid_new(); + GtkWidget *right_grid = gtk_grid_new(); + + // Set direction of the right-hand grid since we're using gtk_container_add() to add contents + gtk_orientable_set_orientation(GTK_ORIENTABLE(right_grid), GTK_ORIENTATION_VERTICAL); + + gtk_grid_set_row_spacing(GTK_GRID(left_grid), 5); + gtk_grid_set_row_spacing(GTK_GRID(right_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(left_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(right_grid), 5); + + // The 3 waveforms on the right pane should all be equal height + gtk_grid_set_row_homogeneous(GTK_GRID(right_grid), TRUE); + + // Add the grids to the two panes + gtk_paned_pack1(GTK_PANED(panes), left_grid, TRUE, FALSE); + gtk_paned_pack2(GTK_PANED(panes), right_grid, TRUE, FALSE); + + gtk_widget_set_size_request(left_grid, 100, -1); // Minimum size of left pane + gtk_widget_set_size_request(right_grid, 200, 300); // Minimum size of right pane + + // Paperstrip + w->paperstrip_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) + g_signal_connect(w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); + gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); + gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); + + // CLEAR button + GtkWidget *clear_button = gtk_button_new_with_label("Clear"); + gtk_widget_set_name(clear_button, "clear_button"); // To allow for CSS styling + gtk_container_set_border_width(GTK_CONTAINER(clear_button), 2); + g_signal_connect(clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), clear_button, 0,1,1,1); + + // CENTER button + GtkWidget *center_button = gtk_button_new_with_label("Center"); + gtk_widget_set_name(center_button, "center_button"); // To allow for CSS styling + gtk_container_set_border_width(GTK_CONTAINER(center_button), 2); + g_signal_connect(center_button, "clicked", G_CALLBACK(handle_center_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), center_button, 1,1,1,1); + + // Tic waveform area + w->tic_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->tic_drawing_area, 400, 100); + g_signal_connect(w->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), w); + gtk_widget_set_hexpand(w->tic_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->tic_drawing_area, TRUE); + gtk_container_add(GTK_CONTAINER(right_grid), w->tic_drawing_area); + + // Toc waveform area + w->toc_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->toc_drawing_area, 400, 100); + g_signal_connect(w->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), w); + gtk_widget_set_hexpand(w->toc_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->toc_drawing_area, TRUE); + gtk_container_add(GTK_CONTAINER(right_grid), w->toc_drawing_area); + + // Period waveform area + w->period_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->period_drawing_area, 400, 100); + g_signal_connect(w->period_drawing_area, "draw", G_CALLBACK(period_draw_event), w); + gtk_widget_set_hexpand(w->period_drawing_area, TRUE); // Make sure we expand when pane resizes + gtk_widget_set_vexpand(w->period_drawing_area, TRUE); + gtk_container_add(GTK_CONTAINER(right_grid), w->period_drawing_area); + #ifdef DEBUG - w->debug_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->debug_drawing_area, 500, 100); - g_signal_connect (w->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), w); - - gtk_container_add (GTK_CONTAINER(settings_grid), w->debug_drawing_area); + w->fps_label = gtk_label_new("0 fps"); + gtk_container_add(GTK_CONTAINER(info_grid), w->fps_label); + + w->debug_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(w->debug_drawing_area, 400, 100); + g_signal_connect(w->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), w); + gtk_container_add(GTK_CONTAINER(right_grid), w->debug_drawing_area); #endif - - // Info area - w->info_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->info_drawing_area, 720, OUTPUT_WINDOW_HEIGHT); - g_signal_connect (w->info_drawing_area, "draw", G_CALLBACK(info_draw_event), w); - - // Populate the panes - GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); - gtk_paned_set_wide_handle(GTK_PANED(panes), TRUE); - - GtkWidget *left_grid = gtk_grid_new(); - GtkWidget *right_grid = gtk_grid_new(); - - // Set direction of the right grid since we're using gtk_container_add() to add contents - gtk_orientable_set_orientation (GTK_ORIENTABLE (right_grid), GTK_ORIENTATION_VERTICAL); - - gtk_grid_set_row_spacing(GTK_GRID(left_grid), 5); - gtk_grid_set_row_spacing(GTK_GRID(right_grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(left_grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(right_grid), 5); - - // The 3 waveforms on the right pane should all be equal height - gtk_grid_set_row_homogeneous(GTK_GRID(right_grid), TRUE); - - // Add the grids to the two panes - gtk_paned_pack1(GTK_PANED(panes), left_grid, TRUE, FALSE); - gtk_paned_pack2(GTK_PANED(panes), right_grid, TRUE, FALSE); - - gtk_widget_set_size_request(left_grid, 100, -1); // Minimum size of left pane - gtk_widget_set_size_request(right_grid, 200, 300); // Minimum size of right pane - - // Paperstrip - w->paperstrip_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) - g_signal_connect (w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); - gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes - gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); - gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); - - // CLEAR button - GtkWidget *clear_button = gtk_button_new_with_label("Clear"); - gtk_widget_set_name(clear_button, "clear_button"); // To allow for CSS styling - gtk_container_set_border_width (GTK_CONTAINER(clear_button), 2); - g_signal_connect (clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); - gtk_grid_attach(GTK_GRID(left_grid), clear_button, 0,1,1,1); - - // CENTER button - GtkWidget *center_button = gtk_button_new_with_label("Center"); - gtk_widget_set_name(center_button, "center_button"); // To allow for CSS styling - gtk_container_set_border_width (GTK_CONTAINER(center_button), 2); - g_signal_connect (center_button, "clicked", G_CALLBACK(handle_center_trace), w); - gtk_grid_attach(GTK_GRID(left_grid), center_button, 1,1,1,1); - - // Tic waveform area - w->tic_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->tic_drawing_area, 400, 100); - g_signal_connect (w->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), w); - gtk_widget_set_hexpand(w->tic_drawing_area, TRUE); // Make sure we expand when pane resizes - gtk_widget_set_vexpand(w->tic_drawing_area, TRUE); - gtk_container_add (GTK_CONTAINER(right_grid), w->tic_drawing_area); - - // Toc waveform area - w->toc_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->toc_drawing_area, 400, 100); - g_signal_connect (w->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), w); - gtk_widget_set_hexpand(w->toc_drawing_area, TRUE); // Make sure we expand when pane resizes - gtk_widget_set_vexpand(w->toc_drawing_area, TRUE); - gtk_container_add (GTK_CONTAINER(right_grid), w->toc_drawing_area); - - // Period waveform area - w->period_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->period_drawing_area, 400, 100); - g_signal_connect (w->period_drawing_area, "draw", G_CALLBACK(period_draw_event), w); - gtk_widget_set_hexpand(w->period_drawing_area, TRUE); // Make sure we expand when pane resizes - gtk_widget_set_vexpand(w->period_drawing_area, TRUE); - gtk_container_add (GTK_CONTAINER(right_grid), w->period_drawing_area); - - - // Populate the root grid with the grids we created above - GtkWidget *root_grid = gtk_grid_new(); // The grid containing all of the UI - gtk_orientable_set_orientation (GTK_ORIENTABLE (root_grid), GTK_ORIENTATION_VERTICAL); - gtk_grid_set_row_spacing(GTK_GRID(root_grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(root_grid), 10); - gtk_container_add(GTK_CONTAINER(w->window), root_grid); // Add the root grid to the window - - gtk_container_add (GTK_CONTAINER(root_grid), settings_grid); - gtk_container_add (GTK_CONTAINER(root_grid), w->info_drawing_area); - gtk_container_add (GTK_CONTAINER(root_grid), panes); - - // All done. Show all the widgets. - gtk_widget_show_all (w->window); - - // gtk_window_set_interactive_debugging(TRUE); - // gtk_window_maximize(GTK_WINDOW(w->window)); + + // Populate the root grid with the grids we created above + GtkWidget *root_grid = gtk_grid_new(); // The grid containing all of the UI + gtk_orientable_set_orientation(GTK_ORIENTABLE(root_grid), GTK_ORIENTATION_VERTICAL); + gtk_grid_set_row_spacing(GTK_GRID(root_grid), 5); + gtk_grid_set_column_spacing(GTK_GRID(root_grid), 10); + gtk_container_add(GTK_CONTAINER(w->window), root_grid); // Add the root grid to the window + + gtk_container_add(GTK_CONTAINER(root_grid), settings_grid); + gtk_container_add(GTK_CONTAINER(root_grid), info_grid); + gtk_container_add(GTK_CONTAINER(root_grid), panes); + + // All done. Show all the widgets. + gtk_widget_show_all(w->window); + + // gtk_window_set_interactive_debugging(TRUE); + // gtk_window_maximize(GTK_WINDOW(w->window)); } /* Called when the GTK application starts running */ static void activate (GtkApplication* app, gpointer user_data) { - int nominal_sr; - double real_sr; - - // Initialize audio - if (start_portaudio(&nominal_sr, &real_sr)) return; // Bail out if we can't open audio. - - struct processing_buffers p[NSTEPS]; - int i; - for(i=0; i < NSTEPS; i++) { - p[i].sample_rate = nominal_sr; - p[i].sample_count = nominal_sr * (1<<(i+FIRST_STEP)); - setup_buffers(&p[i]); - p[i].period = -1; - } - - // Initialize the "global" w object - struct main_window w; - w.sample_rate = real_sr; - w.bfs = p; - w.old = NULL; - w.window = gtk_application_window_new (app); - - // Use the dark theme - g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); - - GtkCssProvider *provider = gtk_css_provider_new(); - GFile *css_file = g_file_new_for_commandline_arg("tg.css"); - gtk_css_provider_load_from_file(provider, css_file, NULL); // No error handling yet! - - GdkDisplay *display = gdk_display_get_default(); - GdkScreen *screen = gdk_display_get_default_screen(display); - gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - g_object_unref(provider); - g_object_unref(css_file); - - initialize_palette(w.window); // Set up the color definitions we'll be using - - // Set up GDK+ widgets for the UI - init_main_window(&w); - - // Call refresh_window() 10 times/second - g_timeout_add_full(G_PRIORITY_LOW, 100, (GSourceFunc)refresh_window, &w, NULL); - - // All GTK applications must have a gtk_main(). Control ends here and waits for an event to occur. - gtk_main(); // Runs the main loop until gtk_main_quit() is called. + int nominal_sr; + double real_sr; + + // Initialize audio + if (start_portaudio(&nominal_sr, &real_sr)) return; // Bail out if we can't open audio. + + struct processing_buffers p[NSTEPS]; + int i; + for(i=0; i < NSTEPS; i++) { + p[i].sample_rate = nominal_sr; + p[i].sample_count = nominal_sr * (1<<(i+FIRST_STEP)); + setup_buffers(&p[i]); + p[i].period = -1; + } + + // Initialize the "global" w object + struct main_window w; + w.sample_rate = real_sr; + w.bfs = p; + w.old = NULL; + w.window = gtk_application_window_new(app); + + // Use the dark theme + g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); + + GtkCssProvider *provider = gtk_css_provider_new(); + GFile *css_file = g_file_new_for_commandline_arg("tg.css"); + gtk_css_provider_load_from_file(provider, css_file, NULL); // No error handling yet! + + GdkDisplay *display = gdk_display_get_default(); + GdkScreen *screen = gdk_display_get_default_screen(display); + gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_object_unref(provider); + g_object_unref(css_file); + + initialize_palette(w.window); // Set up the color definitions we'll be using + + // Set up GDK+ widgets for the UI + init_main_window(&w); + + // Call refresh_window() 10 times/second + g_timeout_add_full(G_PRIORITY_LOW, 100,(GSourceFunc)refresh_window, &w, NULL); + + // All GTK applications must have a gtk_main(). Control ends here and waits for an event to occur. + gtk_main(); // Runs the main loop until gtk_main_quit() is called. } /* PROGRAM START */ int main(int argc, char **argv) { - GtkApplication *app; - int status; - - app = gtk_application_new ("li.ciovil.tg", G_APPLICATION_FLAGS_NONE); // TODO: app id? - - g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); - status = g_application_run (G_APPLICATION (app), argc, argv); - g_object_unref (app); - - return status; + GtkApplication *app; + int status; + + app = gtk_application_new("li.ciovil.tg", G_APPLICATION_FLAGS_NONE); // TODO: app id? + + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + + return status; } diff --git a/tg.css b/tg.css index 23dc373..27e111a 100644 --- a/tg.css +++ b/tg.css @@ -14,6 +14,22 @@ -GtkPaned-handle-size: 10; } +#rate { + font-size: 40px; +} + +#beaterror { + font-size: 40px; +} + +#amplitude { + font-size: 40px; +} + +#bph { + font-size: 40px; +} + #clear_button { /* font-size: 16px; */ } From 6ba5f362fa160a7fbfc9f84daf013edefad9289a Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 3 Jan 2016 02:53:39 +0100 Subject: [PATCH 08/25] Remove unused print_number function. --- interface.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/interface.c b/interface.c index d1ad317..bcddd7a 100644 --- a/interface.c +++ b/interface.c @@ -375,22 +375,6 @@ void cairo_init(cairo_t *c) cairo_paint(c); } -double print_number(cairo_t *c, double x, double y, char *s) -{ - cairo_text_extents_t extents; - cairo_text_extents(c,"0",&extents); - double z = extents.x_advance; - char t[2]; - t[1] = 0; - while((t[0] = *s++)) { - cairo_text_extents(c,t,&extents); - cairo_move_to(c, x + (z - extents.x_advance) / 2, y); - cairo_show_text(c,t); - x += z; - } - return x; -} - /* Draws either the tic or toc waveform & grid in their respective widget */ void draw_waveform( struct main_window *w, @@ -976,6 +960,7 @@ void init_main_window(struct main_window *w) // Populate the panes GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + // gtk_paned_set_wide_handle(GTK_PANED(panes), TRUE); // Requires GTK+ 3.16 but makes the gutter area transparent instead of darker. GtkWidget *left_grid = gtk_grid_new(); GtkWidget *right_grid = gtk_grid_new(); From 239f4412cd5943271f12f163fc0ccab60580fb5f Mon Sep 17 00:00:00 2001 From: Rob Date: Sun, 3 Jan 2016 14:37:37 +0100 Subject: [PATCH 09/25] Alternate colors for tic/tocs in the paperstrip. --- interface.c | 34 +++++++++++++++++++--------------- tg.css | 22 ++++++++++++---------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/interface.c b/interface.c index bcddd7a..9705588 100644 --- a/interface.c +++ b/interface.c @@ -22,7 +22,7 @@ int preset_bph[] = PRESET_BPH; -cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color,*icon1,*icon2,*text; +cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color,*icon1,*icon2,*text_color,*tick_color,*tock_color; void print_debug(char *format,...) { @@ -87,11 +87,15 @@ void initialize_palette(GtkWidget *win) define_color(&range_color, color); gtk_style_context_lookup_color(sc, "text", &color); - define_color(&text, color); + define_color(&text_color, color); gtk_style_context_lookup_color(sc, "icon_on", &color); define_color(&icon1, color); gtk_style_context_lookup_color(sc, "icon_off", &color); define_color(&icon2, color); + gtk_style_context_lookup_color(sc, "tick", &color); + define_color(&tick_color, color); + gtk_style_context_lookup_color(sc, "tock", &color); + define_color(&tock_color, color); } struct main_window { @@ -409,7 +413,7 @@ void draw_waveform( } // Draw numbers for time scale, every 5 ms - cairo_set_source(cr, text); + cairo_set_source(cr, text_color); for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { if(!(i%5)) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); @@ -448,7 +452,7 @@ void draw_waveform( // Draw numbers for amplitude scale double last_x = 0; - cairo_set_source(cr, text); + cairo_set_source(cr, text_color); for(i = 50; i < 360; i+=50) { double t = period*amplitude_to_time(w->la, i); if(t > .001 * NEGATIVE_SPAN) continue; @@ -723,9 +727,9 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo double ten_s = w->sample_rate * 10 / sweep; double last_line = fmod(now/sweep, ten_s); int last_tenth = floor(now/(sweep*ten_s)); - for(i=0;;i++) { + for (i=0;;i++) { double y = 0.5 + round(last_line + i*ten_s); - if(y > height) break; + if (y > height) break; cairo_move_to(cr, .5, y); cairo_line_to(cr, width-.5, y); cairo_set_source(cr, (last_tenth-i)%6 ? grid_color : grid2_color); @@ -733,20 +737,20 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo } // Plot the tick/tocks on the paperstrip - cairo_set_source(cr, stopped ? stopped_color : waveform_color); - for(i = w->events_wp;;) { - if(!w->events[i]) break; + for (i = w->events_wp;;) { + if (!w->events[i]) break; double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); int row = floor(event / sweep); - if(row >= height) break; + if (row >= height) break; + cairo_set_source(cr, i%2 ? tick_color : tock_color); // Alternate colors cairo_move_to(cr,column,row); cairo_line_to(cr,column+1,row); cairo_line_to(cr,column+1,row+1); cairo_line_to(cr,column,row+1); cairo_line_to(cr,column,row); cairo_fill(cr); - if(column < width - strip_width && row > 0) { + if (column < width - strip_width && row > 0) { column += strip_width; row -= 1; cairo_move_to(cr,column,row); @@ -756,12 +760,12 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_line_to(cr,column,row); cairo_fill(cr); } - if(--i < 0) i = EVENTS_COUNT - 1; - if(i == w->events_wp) break; + if (--i < 0) i = EVENTS_COUNT - 1; + if (i == w->events_wp) break; } // Draw the arrowed line for the ms scale at the bottom - cairo_set_source(cr, text); + cairo_set_source(cr, text_color); cairo_set_line_width(cr, 2); cairo_move_to(cr, left_margin + 3, height - 20.5); cairo_line_to(cr, right_margin - 3, height - 20.5); @@ -780,7 +784,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo // Draw the ms scale at the bottom of the paperstrip int fontsize = gtk_widget_get_allocated_width(w->window) / 90; - if(fontsize < 12) + if (fontsize < 12) fontsize = 12; cairo_set_font_size(cr, fontsize); diff --git a/tg.css b/tg.css index 27e111a..c8c5d0a 100644 --- a/tg.css +++ b/tg.css @@ -1,13 +1,15 @@ -@define-color graph_background rgb(0, 0, 0); /* black */ -@define-color grid_line rgb(0, 80%, 0); /* green */ -@define-color grid_line_alternate rgb(100%, 0, 0); /* red */ -@define-color waveform_active rgb(255, 255, 255); /* white */ -@define-color waveform_stopped rgb(255, 255, 0); /* yellow */ -@define-color pulse rgb(0, 0, 100%); /* blue */ -@define-color pulse_range rgb(0, 0, 50%); /* blueish */ -@define-color icon_on rgb(0, 100%, 0); /* green */ -@define-color icon_off rgb(100%, 0, 0); /* red */ -@define-color text rgb(255, 255, 255); /* white */ +@define-color graph_background rgb(0, 0, 0); /* black */ +@define-color grid_line rgb(0, 80%, 0); /* green */ +@define-color grid_line_alternate rgb(100%, 0, 0); /* red */ +@define-color waveform_active rgb(255, 255, 255); /* white */ +@define-color waveform_stopped rgb(255, 255, 0); /* yellow */ +@define-color pulse rgb(0, 0, 100%); /* blue */ +@define-color pulse_range rgb(0, 0, 50%); /* blueish */ +@define-color icon_on rgb(0, 100%, 0); /* green */ +@define-color icon_off rgb(100%, 0, 0); /* red */ +@define-color text rgb(255, 255, 255); /* white */ +@define-color tick rgb(128, 180, 255); /* pale blue */ +@define-color tock rgb(220, 220, 100); /* pale yellow */ * { From 67253312065283f8ddbcdf8630b096d6149c06cc Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 8 Jan 2016 04:27:43 +0100 Subject: [PATCH 10/25] Start of settings window + mouse wheel paperstrip zooming. --- audio.c | 28 +++++++++++ interface.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++------ tg.h | 3 +- 3 files changed, 153 insertions(+), 17 deletions(-) diff --git a/audio.c b/audio.c index 3dd19a3..24d5a6e 100644 --- a/audio.c +++ b/audio.c @@ -126,3 +126,31 @@ int analyze_pa_data(struct processing_buffers *p, int bph, uint64_t events_from) debug("---\n"); return i; } + +/* Returns the number of input sources */ +int num_inputs() { + int inputs = 0; + // Gather list of hosts that have inputs. + int nDevices = Pa_GetDeviceCount(); + for (int dev = 0; dev < nDevices; dev++) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(dev); + if (info->maxInputChannels > 0) { + inputs++; + } + } + return inputs; +} + +/* Get name of specified input */ +const char * input_name(int i) { + int num = 0; + int nDevices = Pa_GetDeviceCount(); + for (int dev = 0; dev < nDevices; dev++) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(dev); + if (info->maxInputChannels > 0) { + // Only count source that has input + if (++num == i) return info->name; + } + } + return NULL; +} diff --git a/interface.c b/interface.c index 9705588..3941454 100644 --- a/interface.c +++ b/interface.c @@ -129,6 +129,7 @@ struct main_window { int events_wp; uint64_t events_from; double trace_centering; + int trace_zoom; int signal; }; @@ -371,12 +372,12 @@ double amplitude_to_time(double lift_angle, double amp) } /* Set up default line width and background color before each drawing operation */ -void cairo_init(cairo_t *c) +void cairo_init(cairo_t *cr) { - cairo_set_line_width(c, 1); + cairo_set_line_width(cr, 1); - cairo_set_source(c, bg_color); - cairo_paint(c); + cairo_set_source(cr, bg_color); + cairo_paint(cr); } /* Draws either the tic or toc waveform & grid in their respective widget */ @@ -680,7 +681,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo if (p && w->events[w->events_wp]) { double rate = get_rate(w->guessed_bph, w->sample_rate, p); - double slope = - rate * strip_width * PAPERSTRIP_ZOOM / (3600. * 24.); + double slope = - rate * strip_width * w->trace_zoom / (3600. * 24.); if (slope <= 1 && slope >= -1) { for (i=0; i<4; i++) { double y = 0; @@ -739,8 +740,9 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo // Plot the tick/tocks on the paperstrip for (i = w->events_wp;;) { if (!w->events[i]) break; - double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * PAPERSTRIP_ZOOM); - int column = floor(fmod(event, (sweep / PAPERSTRIP_ZOOM)) * strip_width / (sweep / PAPERSTRIP_ZOOM)); + double event = now - w->events[i] + w->trace_centering + sweep * PAPERSTRIP_MARGIN / (2 * w->trace_zoom); + int column = floor(fmod(event, (sweep / w->trace_zoom)) * strip_width / (sweep / w->trace_zoom)); + int row = floor(event / sweep); if (row >= height) break; cairo_set_source(cr, i%2 ? tick_color : tock_color); // Alternate colors @@ -749,8 +751,10 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_line_to(cr,column+1,row+1); cairo_line_to(cr,column,row+1); cairo_line_to(cr,column,row); + // cairo_rectangle (cr, column, row, 1, 1); cairo_fill(cr); if (column < width - strip_width && row > 0) { + // Replicate the dot to the right if it's in the margin. column += strip_width; row -= 1; cairo_move_to(cr,column,row); @@ -791,7 +795,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo char s[50]; cairo_text_extents_t extents; - snprintf(s, 50, "%.1f ms", 3600000. / (w->guessed_bph * PAPERSTRIP_ZOOM)); + snprintf(s, 50, "%.1f ms", 3600000. / (w->guessed_bph * w->trace_zoom)); cairo_text_extents(cr,s,&extents); cairo_move_to(cr, (width - extents.x_advance)/2, height - 30); cairo_show_text(cr, s); @@ -854,16 +858,20 @@ void handle_clear_trace(GtkButton *b, struct main_window *w) redraw(w); } -/* Called when the user clicks the Center button */ -void handle_center_trace(GtkButton *b, struct main_window *w) -{ +void center_trace(struct main_window *w) { uint64_t last_ev = w->events[w->events_wp]; if(last_ev) { - double sweep = w->sample_rate * 3600. / (PAPERSTRIP_ZOOM * w->guessed_bph); + double sweep = w->sample_rate * 3600. / (w->trace_zoom * w->guessed_bph); w->trace_centering = fmod(last_ev + .5*sweep , sweep); } else w->trace_centering = 0; - redraw(w); +} + +/* Called when the user clicks the Center button */ +void handle_center_trace(GtkButton *b, struct main_window *w) +{ + center_trace(w); + gtk_widget_queue_draw(w->paperstrip_drawing_area); } void quit() @@ -871,6 +879,96 @@ void quit() gtk_main_quit(); } +/* Called when the user scrolls the mouse wheel over the paperstrip */ +gboolean paperstrip_scroll_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) +{ + if (event->scroll.direction == GDK_SCROLL_UP) { + w->trace_zoom++; + if (w->trace_zoom >20) w->trace_zoom = 20; + } else + if (event->scroll.direction == GDK_SCROLL_DOWN) { + w->trace_zoom--; + if (w->trace_zoom <1) w->trace_zoom = 1; + } + + center_trace(w); + + return TRUE; // Stop event propagation +} + +void show_preferences(GtkButton *button, struct main_window *w) { + GtkWidget *dialog; + GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; + dialog = gtk_dialog_new_with_buttons ("Settings", + GTK_WINDOW(w->window), + flags, + "OK", + GTK_RESPONSE_ACCEPT, + NULL); + + // gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); // Non-resizable + // Ensure that the dialog box is destroyed when the user responds + g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_set_border_width(GTK_CONTAINER(content_area), 5); + GtkWidget *prefs_grid = gtk_grid_new(); + gtk_grid_set_column_spacing(GTK_GRID(prefs_grid), 6); + gtk_grid_set_row_spacing(GTK_GRID(prefs_grid), 4); + gtk_container_set_border_width(GTK_CONTAINER(prefs_grid), 10); + gtk_widget_set_halign(prefs_grid, GTK_ALIGN_CENTER); // Keep grid centered in case user resizes window + gtk_container_add(GTK_CONTAINER(content_area), prefs_grid); // Add the grid to the dialog + + GtkWidget *input_label = gtk_label_new("Audio input:"); + gtk_widget_set_tooltip_text(input_label, "What audio input to listen to."); + gtk_widget_set_halign(input_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), input_label, 0,0,1,1); + + GtkWidget *adjust_label = gtk_label_new("Rate adjustment:"); + gtk_widget_set_tooltip_text(adjust_label, "Sound card sampling rate correction."); + gtk_widget_set_halign(adjust_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), adjust_label, 0,1,1,1); + + GtkWidget *cpu_label = gtk_label_new("Precision mode:"); + gtk_widget_set_tooltip_text(cpu_label, "Use more CPU to enhance calculations."); + gtk_widget_set_halign(cpu_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), cpu_label, 0,2,1,1); + + GtkWidget *audio_label = gtk_label_new("Audio:"); + gtk_widget_set_tooltip_text(audio_label, "Audible clicks for each beat detected."); + gtk_widget_set_halign(audio_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), audio_label, 0,3,1,1); + + + GtkWidget *input = gtk_combo_box_text_new(); + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(input), "Default"); + for (int n=1; n <= num_inputs(); n++) { + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(input), input_name(n)); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(input), 0); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), input, input_label, GTK_POS_RIGHT,2,1); + + GtkWidget *adjustment = gtk_entry_new(); + gtk_entry_set_input_purpose(GTK_ENTRY(adjustment), GTK_INPUT_PURPOSE_NUMBER); + gtk_entry_set_alignment(GTK_ENTRY(adjustment), 1); // Right aligned + gtk_entry_set_max_length(GTK_ENTRY(adjustment), 6); + gtk_entry_set_width_chars(GTK_ENTRY(adjustment), 6); + gtk_entry_set_max_width_chars(GTK_ENTRY(adjustment), 6); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), adjustment, adjust_label, GTK_POS_RIGHT, 1, 1); + + GtkWidget *secs = gtk_label_new("seconds/day"); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), secs, adjustment, GTK_POS_RIGHT,1,1); + + GtkWidget *cpu = gtk_check_button_new(); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), cpu, cpu_label, GTK_POS_RIGHT, 2,1); + + GtkWidget *audio = gtk_check_button_new(); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audio, audio_label, GTK_POS_RIGHT, 2, 1); + + gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); // Show the dialog +} + /* Set up the main window and populate with widgets */ void init_main_window(struct main_window *w) { @@ -881,6 +979,7 @@ void init_main_window(struct main_window *w) w->events_wp = 0; w->events_from = 0; w->trace_centering = 0; + w->trace_zoom = 10; w->guessed_bph = w->last_bph = DEFAULT_BPH; w->bph = 0; @@ -927,6 +1026,13 @@ void init_main_window(struct main_window *w) g_signal_connect(w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); gtk_container_add(GTK_CONTAINER(settings_grid), w->la_spin_button); + // Prefs button + GtkWidget *prefs_button = gtk_button_new_with_label("Settings"); + gtk_widget_set_halign(prefs_button, GTK_ALIGN_END); // Right aligned + gtk_widget_set_hexpand(prefs_button, TRUE); // Needed to make the grid expand and make room for the button at the far right + g_signal_connect(prefs_button, "clicked", G_CALLBACK(show_preferences), w); + gtk_container_add(GTK_CONTAINER(settings_grid), prefs_button); + // Info grid GtkWidget *info_grid = gtk_grid_new(); // The grid containing the info text, default to horizontal orientation gtk_grid_set_column_spacing(GTK_GRID(info_grid), 20); @@ -989,8 +1095,10 @@ void init_main_window(struct main_window *w) // Paperstrip w->paperstrip_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons (~150px) + gtk_widget_set_size_request(w->paperstrip_drawing_area, 100, 300); // Min width is actually limited by the buttons below (~150px) + gtk_widget_add_events(w->paperstrip_drawing_area, GDK_SCROLL_MASK); // React to mouse wheel scroll events g_signal_connect(w->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), w); + g_signal_connect(w->paperstrip_drawing_area, "scroll-event", G_CALLBACK(paperstrip_scroll_event), w); gtk_widget_set_hexpand(w->paperstrip_drawing_area, TRUE); // Make sure we expand when pane resizes gtk_widget_set_vexpand(w->paperstrip_drawing_area, TRUE); gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); @@ -1061,7 +1169,6 @@ void init_main_window(struct main_window *w) // gtk_window_maximize(GTK_WINDOW(w->window)); } - /* Called when the GTK application starts running */ static void activate (GtkApplication* app, gpointer user_data) { @@ -1105,7 +1212,7 @@ static void activate (GtkApplication* app, gpointer user_data) // Set up GDK+ widgets for the UI init_main_window(&w); - + // Call refresh_window() 10 times/second g_timeout_add_full(G_PRIORITY_LOW, 100,(GSourceFunc)refresh_window, &w, NULL); diff --git a/tg.h b/tg.h index b26c3c2..e5daa0c 100644 --- a/tg.h +++ b/tg.h @@ -50,7 +50,6 @@ #define EVENTS_COUNT 10000 #define EVENTS_MAX 100 -#define PAPERSTRIP_ZOOM 10 #define PAPERSTRIP_MARGIN .2 #define MIN_BPH 12000 @@ -100,6 +99,8 @@ void process(struct processing_buffers *p, int bph); /* audio.c */ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate); int analyze_pa_data(struct processing_buffers *p, int bph, uint64_t events_from); +int num_inputs(); +const char * input_name(int i); /* interface.c */ void print_debug(char *format,...); From 4b894c7c079db0babfc18c105ebef358a2b1e9c3 Mon Sep 17 00:00:00 2001 From: Rob Date: Sat, 9 Jan 2016 02:26:42 +0100 Subject: [PATCH 11/25] Save/load settings to disk. --- interface.c | 101 ++++++++++++++++++++++++++++++++++++---------------- prefs.c | 65 +++++++++++++++++++++++++++++++++ tg.h | 16 +++++++++ tg.ini | 13 +++++++ 4 files changed, 164 insertions(+), 31 deletions(-) create mode 100644 prefs.c create mode 100644 tg.ini diff --git a/interface.c b/interface.c index 3941454..cce5acb 100644 --- a/interface.c +++ b/interface.c @@ -107,6 +107,7 @@ struct main_window { GtkWidget *beaterror_label; GtkWidget *amplitude_label; GtkWidget *bph_label; + GtkWidget *panes; GtkWidget *tic_drawing_area; GtkWidget *toc_drawing_area; GtkWidget *period_drawing_area; @@ -132,6 +133,7 @@ struct main_window { int trace_zoom; int signal; + struct Settings conf; }; /* Redraw the DrawingArea widgets (waveforms etc.) */ @@ -293,13 +295,6 @@ guint refresh_window(struct main_window *w) return TRUE; } -gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) -{ - // If you return FALSE in the "delete-event" signal handler - // GTK will emit the "destroy" signal. - return FALSE; -} - /* Draw the audio waveform */ void draw_graph(double a, double b, cairo_t *cr, struct processing_buffers *p, GtkWidget *da) { @@ -874,8 +869,23 @@ void handle_center_trace(GtkButton *b, struct main_window *w) gtk_widget_queue_draw(w->paperstrip_drawing_area); } -void quit() +gboolean delete_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) { + // Save UI size/location settings + w->conf.window_width = gtk_widget_get_allocated_width(GTK_WIDGET(widget)); + w->conf.window_height = gtk_widget_get_allocated_height(GTK_WIDGET(widget)); + w->conf.pane_pos = gtk_paned_get_position(GTK_PANED(w->panes)); + save_settings(&w->conf); + + // If you return FALSE in the "delete-event" signal handler + // GTK will emit the "destroy" signal. + return FALSE; +} + +void quit(GtkWidget *widget, struct main_window *w) +{ + // (Can't save window sizes here because it's destroyed by now) + // We save that on the delete signal instead gtk_main_quit(); } @@ -896,6 +906,7 @@ gboolean paperstrip_scroll_event(GtkWidget *widget, GdkEvent *event, struct main return TRUE; // Stop event propagation } +/* Display the Settings dialog */ void show_preferences(GtkButton *button, struct main_window *w) { GtkWidget *dialog; GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; @@ -907,8 +918,6 @@ void show_preferences(GtkButton *button, struct main_window *w) { NULL); // gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); // Non-resizable - // Ensure that the dialog box is destroyed when the user responds - g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_container_set_border_width(GTK_CONTAINER(content_area), 5); @@ -934,18 +943,24 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_widget_set_halign(cpu_label, GTK_ALIGN_END); // Right aligned gtk_grid_attach(GTK_GRID(prefs_grid), cpu_label, 0,2,1,1); - GtkWidget *audio_label = gtk_label_new("Audio:"); - gtk_widget_set_tooltip_text(audio_label, "Audible clicks for each beat detected."); - gtk_widget_set_halign(audio_label, GTK_ALIGN_END); // Right aligned - gtk_grid_attach(GTK_GRID(prefs_grid), audio_label, 0,3,1,1); + GtkWidget *dark_label = gtk_label_new("Dark UI:"); + gtk_widget_set_tooltip_text(dark_label, "Use a darker user interface with less light pollution."); + gtk_widget_set_halign(dark_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), dark_label, 0,3,1,1); + GtkWidget *ticks_label = gtk_label_new("Clicks:"); + gtk_widget_set_tooltip_text(ticks_label, "Audible clicks for each beat detected."); + gtk_widget_set_halign(ticks_label, GTK_ALIGN_END); // Right aligned + gtk_grid_attach(GTK_GRID(prefs_grid), ticks_label, 0,4,1,1); GtkWidget *input = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(input), "Default"); + int active = 0; for (int n=1; n <= num_inputs(); n++) { gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(input), input_name(n)); + if (strcmp(w->conf.audio_input, input_name(n)) == 0) active=n; } - gtk_combo_box_set_active(GTK_COMBO_BOX(input), 0); + gtk_combo_box_set_active(GTK_COMBO_BOX(input), active); // Fill in value from settings gtk_grid_attach_next_to(GTK_GRID(prefs_grid), input, input_label, GTK_POS_RIGHT,2,1); GtkWidget *adjustment = gtk_entry_new(); @@ -954,19 +969,41 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_entry_set_max_length(GTK_ENTRY(adjustment), 6); gtk_entry_set_width_chars(GTK_ENTRY(adjustment), 6); gtk_entry_set_max_width_chars(GTK_ENTRY(adjustment), 6); + char ra[12]; + snprintf(ra, 12, "%+.2f", w->conf.rate_adjustment); + gtk_entry_set_text(GTK_ENTRY(adjustment), ra); // Fill in value from settings gtk_grid_attach_next_to(GTK_GRID(prefs_grid), adjustment, adjust_label, GTK_POS_RIGHT, 1, 1); GtkWidget *secs = gtk_label_new("seconds/day"); gtk_grid_attach_next_to(GTK_GRID(prefs_grid), secs, adjustment, GTK_POS_RIGHT,1,1); GtkWidget *cpu = gtk_check_button_new(); + if (w->conf.precision_mode) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu), TRUE); gtk_grid_attach_next_to(GTK_GRID(prefs_grid), cpu, cpu_label, GTK_POS_RIGHT, 2,1); - GtkWidget *audio = gtk_check_button_new(); - gtk_grid_attach_next_to(GTK_GRID(prefs_grid), audio, audio_label, GTK_POS_RIGHT, 2, 1); + GtkWidget *dark = gtk_check_button_new(); + if (w->conf.dark_theme) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dark), TRUE); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), dark, dark_label, GTK_POS_RIGHT, 2, 1); + + GtkWidget *ticks = gtk_check_button_new(); + if (w->conf.ticks) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ticks), TRUE); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), ticks, ticks_label, GTK_POS_RIGHT, 2, 1); gtk_widget_show_all(dialog); + gtk_dialog_run(GTK_DIALOG(dialog)); // Show the dialog + + // Save the dialog data in the settings variable and then save it out to disk + w->conf.audio_input = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(input)); + w->conf.rate_adjustment = atof(gtk_entry_get_text(GTK_ENTRY(adjustment))); + w->conf.precision_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cpu)); + w->conf.dark_theme = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dark)); + w->conf.ticks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ticks)); + + save_settings(&w->conf); + + // Get rid of the dialog + gtk_widget_destroy(GTK_WIDGET(dialog)); } /* Set up the main window and populate with widgets */ @@ -986,7 +1023,7 @@ void init_main_window(struct main_window *w) w->la = DEFAULT_LA; gtk_container_set_border_width(GTK_CONTAINER(w->window), 5); // Border around the window - g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), NULL); // Signal emitted if a user requests that a toplevel window is closed. + g_signal_connect(w->window, "delete_event", G_CALLBACK(delete_event), w); // Signal emitted if a user requests that a toplevel window is closed. g_signal_connect(w->window, "destroy", G_CALLBACK(quit), w); gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); @@ -1069,9 +1106,9 @@ void init_main_window(struct main_window *w) gtk_container_add(GTK_CONTAINER(info_grid), w->bph_label); // Add to grid // Populate the panes - GtkWidget *panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + w->panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); // gtk_paned_set_wide_handle(GTK_PANED(panes), TRUE); // Requires GTK+ 3.16 but makes the gutter area transparent instead of darker. - + gtk_paned_set_position(GTK_PANED(w->panes), w->conf.pane_pos); // Set the pane divider according to the settings GtkWidget *left_grid = gtk_grid_new(); GtkWidget *right_grid = gtk_grid_new(); @@ -1087,8 +1124,8 @@ void init_main_window(struct main_window *w) gtk_grid_set_row_homogeneous(GTK_GRID(right_grid), TRUE); // Add the grids to the two panes - gtk_paned_pack1(GTK_PANED(panes), left_grid, TRUE, FALSE); - gtk_paned_pack2(GTK_PANED(panes), right_grid, TRUE, FALSE); + gtk_paned_pack1(GTK_PANED(w->panes), left_grid, TRUE, FALSE); + gtk_paned_pack2(GTK_PANED(w->panes), right_grid, TRUE, FALSE); gtk_widget_set_size_request(left_grid, 100, -1); // Minimum size of left pane gtk_widget_set_size_request(right_grid, 200, 300); // Minimum size of right pane @@ -1160,7 +1197,7 @@ void init_main_window(struct main_window *w) gtk_container_add(GTK_CONTAINER(root_grid), settings_grid); gtk_container_add(GTK_CONTAINER(root_grid), info_grid); - gtk_container_add(GTK_CONTAINER(root_grid), panes); + gtk_container_add(GTK_CONTAINER(root_grid), w->panes); // All done. Show all the widgets. gtk_widget_show_all(w->window); @@ -1170,12 +1207,15 @@ void init_main_window(struct main_window *w) } /* Called when the GTK application starts running */ -static void activate (GtkApplication* app, gpointer user_data) +void activate (GtkApplication* app, gpointer user_data) { + // Initialize the "global" w object + struct main_window w; + load_settings(&w.conf); // Load app settings + + // Initialize audio int nominal_sr; double real_sr; - - // Initialize audio if (start_portaudio(&nominal_sr, &real_sr)) return; // Bail out if we can't open audio. struct processing_buffers p[NSTEPS]; @@ -1187,15 +1227,14 @@ static void activate (GtkApplication* app, gpointer user_data) p[i].period = -1; } - // Initialize the "global" w object - struct main_window w; w.sample_rate = real_sr; w.bfs = p; w.old = NULL; w.window = gtk_application_window_new(app); - - // Use the dark theme - g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); + gtk_window_set_default_size(GTK_WINDOW(w.window), w.conf.window_width, w.conf.window_height); + + if (w.conf.dark_theme) // Use the dark theme + g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); GtkCssProvider *provider = gtk_css_provider_new(); GFile *css_file = g_file_new_for_commandline_arg("tg.css"); diff --git a/prefs.c b/prefs.c new file mode 100644 index 0000000..ddae49a --- /dev/null +++ b/prefs.c @@ -0,0 +1,65 @@ +/* + tg + Copyright (C) 2015 Marcello Mamino + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tg.h" +#include + +static GKeyFile *key_file; // Keep this object alive for the duration of the app + +void load_settings(struct Settings *conf) { + + GError *err = NULL; + key_file = g_key_file_new(); + + if(!g_key_file_load_from_file(key_file, + "tg.ini", + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, + &err)) + { + g_message("Couldn't load settings file: %s", err->message); + g_error_free (err); + } + else { + conf->audio_input = g_key_file_get_string(key_file, "main", "audio_input", &err); + conf->rate_adjustment = g_key_file_get_double(key_file, "main", "rate_adjustment", &err); + conf->precision_mode = g_key_file_get_boolean(key_file, "main", "precision_mode", &err); + conf->ticks = g_key_file_get_boolean(key_file, "main", "ticks", &err); + conf->dark_theme = g_key_file_get_boolean(key_file, "ui", "dark_theme", &err); + conf->window_width = g_key_file_get_integer(key_file, "ui", "window_width", &err); + conf->window_height = g_key_file_get_integer(key_file, "ui", "window_height", &err); + conf->pane_pos = g_key_file_get_integer(key_file, "ui", "pane_pos", &err); + } +} + +void save_settings(struct Settings *conf) { + g_key_file_set_string(key_file, "main", "audio_input", conf->audio_input); + g_key_file_set_double(key_file, "main", "rate_adjustment", conf->rate_adjustment); + g_key_file_set_boolean(key_file, "main", "precision_mode", conf->precision_mode); + g_key_file_set_boolean(key_file, "main", "ticks", conf->ticks); + g_key_file_set_boolean(key_file, "ui", "dark_theme", conf->dark_theme); + g_key_file_set_integer(key_file, "ui", "window_width", conf->window_width); + g_key_file_set_integer(key_file, "ui", "window_height", conf->window_height); + g_key_file_set_integer(key_file, "ui", "pane_pos", conf->pane_pos); + + GError *err = NULL; + if (!g_key_file_save_to_file(key_file, "tg.ini", &err)) { + g_message("Failed to save settings: %s", err->message); + g_error_free (err); + } +} diff --git a/tg.h b/tg.h index e5daa0c..449b5fa 100644 --- a/tg.h +++ b/tg.h @@ -23,6 +23,7 @@ #include #include #include +#include "glib.h" #define FILTER_CUTOFF 3000 @@ -103,5 +104,20 @@ int num_inputs(); const char * input_name(int i); /* interface.c */ +struct Settings +{ + gchar *audio_input; + gdouble rate_adjustment; + gboolean precision_mode; + gboolean ticks; + gboolean dark_theme; + int window_width, window_height, pane_pos; +}; + void print_debug(char *format,...); void error(char *format,...); + +/* prefs.c */ +void load_settings(); +void save_settings(); + diff --git a/tg.ini b/tg.ini new file mode 100644 index 0000000..4be61b5 --- /dev/null +++ b/tg.ini @@ -0,0 +1,13 @@ +# tg settings file + +[main] +audio_input=Default +rate_adjustment=0.0 +precision_mode=true +ticks=false + +[ui] +dark_theme=true +window_width=720 +window_height=480 +pane_pos=150 \ No newline at end of file From ed7a2dcaab022f634ed3d29bedd0f12092c80818 Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 11 Jan 2016 04:06:39 +0100 Subject: [PATCH 12/25] Hooked up audio input settings. --- audio.c | 34 +++++++++++++++++++++++++++++++--- interface.c | 3 ++- tg.h | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/audio.c b/audio.c index 24d5a6e..c9a9d7f 100644 --- a/audio.c +++ b/audio.c @@ -44,7 +44,7 @@ int paudio_callback(const void *input_buffer, } /* Set up PA to continuously sample audio and store in buffers */ -int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) +int start_portaudio(int *nominal_sample_rate, double *real_sample_rate, char* name) { PaStream *stream; @@ -54,8 +54,36 @@ int start_portaudio(int *nominal_sample_rate, double *real_sample_rate) if(err!=paNoError) goto error; - // Open an audio stream with 2 input channels & 0 output channels with an unspecified buffer size - err = Pa_OpenDefaultStream(&stream,2,0,paFloat32,PA_SAMPLE_RATE,paFramesPerBufferUnspecified,paudio_callback,x); + PaStreamParameters inputParameters; + inputParameters.channelCount = 2; // Stereo + inputParameters.sampleFormat = paFloat32; + // inputParameters.suggestedLatency = ; + inputParameters.hostApiSpecificStreamInfo = NULL; + inputParameters.device = Pa_GetDefaultInputDevice(); // Default input device + + // Try to locate the device selected in settings + int nDevices = Pa_GetDeviceCount(); + for (int dev = 0; dev < nDevices; dev++) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(dev); + if (info->maxInputChannels > 0) { + // Only check sources that has inputs + if (strcmp(info->name, name) == 0) { + inputParameters.device = dev; + break; + } + } + } + + // Open the audio stream + err = Pa_OpenStream(&stream, + &inputParameters, + NULL, // outputParameters + PA_SAMPLE_RATE, + paFramesPerBufferUnspecified, // Frames per buffer + paNoFlag, + paudio_callback, + x); + *x = stream; if(err!=paNoError) goto error; diff --git a/interface.c b/interface.c index cce5acb..8e98f20 100644 --- a/interface.c +++ b/interface.c @@ -230,6 +230,7 @@ void set_beaterror_label(struct main_window *w, double be) char output[99]; snprintf(output, 99, "%4.1f ms", be); gtk_label_set_markup(GTK_LABEL(w->beaterror_label), output); + // gtk_widget_set_tooltip_text(w->beaterror_label, "12˚"); // Alternate display } void set_amplitude_label(struct main_window *w, double amp) @@ -1216,7 +1217,7 @@ void activate (GtkApplication* app, gpointer user_data) // Initialize audio int nominal_sr; double real_sr; - if (start_portaudio(&nominal_sr, &real_sr)) return; // Bail out if we can't open audio. + if (start_portaudio(&nominal_sr, &real_sr, w.conf.audio_input)) return; // Bail out if we can't open audio. struct processing_buffers p[NSTEPS]; int i; diff --git a/tg.h b/tg.h index 449b5fa..257bfdc 100644 --- a/tg.h +++ b/tg.h @@ -98,7 +98,7 @@ void pb_destroy_clone(struct processing_buffers *p); void process(struct processing_buffers *p, int bph); /* audio.c */ -int start_portaudio(int *nominal_sample_rate, double *real_sample_rate); +int start_portaudio(int *nominal_sample_rate, double *real_sample_rate, char *name); int analyze_pa_data(struct processing_buffers *p, int bph, uint64_t events_from); int num_inputs(); const char * input_name(int i); From b5dab89384cc36605cd7b67788790bfaad3e0979 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 12 Jan 2016 06:03:36 +0100 Subject: [PATCH 13/25] Stop the timeout when exiting app in order to prevent invalid calls to analyze_pa_data() --- interface.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/interface.c b/interface.c index 8e98f20..45571a0 100644 --- a/interface.c +++ b/interface.c @@ -807,7 +807,7 @@ gboolean debug_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) int old = 0; struct processing_buffers *p = get_data(w, &old); - if(p) { + if (p) { double a = p->period / 10; double b = p->period * 2; @@ -825,11 +825,11 @@ gboolean debug_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) void handle_bph_change(GtkComboBox *b, struct main_window *w) { char *s = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(b)); - if(s) { + if (s) { int n; char *t; n = (int)strtol(s, &t, 10); - if(*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; + if (*t || n < MIN_BPH || n > MAX_BPH) w->bph = 0; else w->bph = w->guessed_bph = n; g_free(s); recompute(w); @@ -842,7 +842,7 @@ void handle_bph_change(GtkComboBox *b, struct main_window *w) void handle_la_change(GtkSpinButton *b, struct main_window *w) { double la = gtk_spin_button_get_value(b); - if(la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; + if (la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; w->la = la; redraw(w); } @@ -856,7 +856,7 @@ void handle_clear_trace(GtkButton *b, struct main_window *w) void center_trace(struct main_window *w) { uint64_t last_ev = w->events[w->events_wp]; - if(last_ev) { + if (last_ev) { double sweep = w->sample_rate * 3600. / (w->trace_zoom * w->guessed_bph); w->trace_centering = fmod(last_ev + .5*sweep , sweep); } else @@ -878,6 +878,9 @@ gboolean delete_event(GtkWidget *widget, GdkEvent *event, struct main_window *w) w->conf.pane_pos = gtk_paned_get_position(GTK_PANED(w->panes)); save_settings(&w->conf); + // Stop timeout + g_source_remove_by_user_data((gpointer)w); + // If you return FALSE in the "delete-event" signal handler // GTK will emit the "destroy" signal. return FALSE; From d73f75a359eb8611bf792894ee7927e6fcddb168 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 12 Jan 2016 19:49:23 +0100 Subject: [PATCH 14/25] Added prefs.c to the Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 195dc6d..7e19557 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ CC = gcc CFLAGS = -Wall -O3 -DVERSION='"$(VERSION)"' `pkg-config --cflags gtk+-3.0 portaudio-2.0 fftw3f` LDFLAGS = -lm `pkg-config --libs gtk+-3.0 portaudio-2.0 fftw3f` -CFILES = interface.c algo.c audio.c +CFILES = interface.c algo.c audio.c prefs.c HFILES = tg.h ALLFILES = $(CFILES) $(HFILES) Makefile From ef9149c5c4db6aaa8153839d3ca28552dfd8fd95 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 15 Jan 2016 01:59:13 +0100 Subject: [PATCH 15/25] Info text alignment improvements. --- interface.c | 132 ++++++++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/interface.c b/interface.c index 45571a0..da05cdb 100644 --- a/interface.c +++ b/interface.c @@ -42,7 +42,7 @@ void error(char *format,...) va_end(args); char *t; - if(size < 100) { + if (size < 100) { t = s; } else { t = alloca(size+1); @@ -126,7 +126,7 @@ struct main_window { double la; double sample_rate; - uint64_t *events; + uint64_t *events; // List of ticks and tocks int events_wp; uint64_t events_from; double trace_centering; @@ -157,9 +157,9 @@ int guess_bph(double period) int i,ret; ret = 0; - for(i=0; preset_bph[i]; i++) { + for (i=0; preset_bph[i]; i++) { double diff = fabs(bph - preset_bph[i]); - if(diff < min) { + if (diff < min) { min = diff; ret = i; } @@ -173,11 +173,11 @@ struct processing_buffers *get_data(struct main_window *w, int *old) { struct processing_buffers *p = w->bfs; int i; - for(i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); - if(i >= 0) { + for (i=0; i=0 && p[i].sigma > p[i].period / 10000; i--); + if (i >= 0) { // TODO: Can't we have one object kept alive instead of destroying/creating new ones every time? - if(w->old) pb_destroy_clone(w->old); // Remove the previous old data + if (w->old) pb_destroy_clone(w->old); // Remove the previous old data w->old = pb_clone(&p[i]); // Store the current data as old *old = 0; return &p[i]; @@ -192,8 +192,8 @@ void recompute(struct main_window *w) w->signal = analyze_pa_data(w->bfs, w->bph, w->events_from); int old; struct processing_buffers *p = get_data(w, &old); - if(old) w->signal = -w->signal; - if(p) + if (old) w->signal = -w->signal; + if (p) // If we have a bph set, use that for the "guess". Otherwise, calculate a guess. w->guessed_bph = w->bph ? w->bph : guess_bph(p->period / w->sample_rate); } @@ -207,10 +207,10 @@ double get_rate(int bph, double sample_rate, struct processing_buffers *p) double get_amplitude(double la, struct processing_buffers *p) { double ret = -1; - if(p->tic_pulse > 0 && p->toc_pulse > 0) { + if (p->tic_pulse > 0 && p->toc_pulse > 0) { double tic_amp = la * .5 / sin(M_PI * p->tic_pulse / p->period); double toc_amp = la * .5 / sin(M_PI * p->toc_pulse / p->period); - if(la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) + if (la < tic_amp && tic_amp < 360 && la < toc_amp && toc_amp < 360 && fabs(tic_amp - toc_amp) < 60) ret = (tic_amp + toc_amp) / 2; } return ret; @@ -305,14 +305,14 @@ void draw_graph(double a, double b, cairo_t *cr, struct processing_buffers *p, G int n; int first = TRUE; - for(n=0; n<2*width; n++) { + for (n=0; n<2*width; n++) { int i = n < width ? n : 2*width - 1 - n; double x = fmod(a + i * (b-a) / width, p->period); if (x < 0) x += p->period; int j = floor(x); double y; - if(p->waveform[j] <= 0) y = 0; + if (p->waveform[j] <= 0) y = 0; else y = p->waveform[j] * 0.4 / p->waveform_max; int k = round(y*height); @@ -337,22 +337,22 @@ void draw_debug_graph(double a, double b, cairo_t *c, struct processing_buffers int ai = round(a); int bi = 1+round(b); - if(ai < 0) ai = 0; - if(bi > p->sample_count) bi = p->sample_count; - for(i=ai; idebug[i] > max) + if (ai < 0) ai = 0; + if (bi > p->sample_count) bi = p->sample_count; + for (i=ai; idebug[i] > max) max = p->debug[i]; int first = 1; - for(i=0; i= p->sample_count) j = p->sample_count-1; + if (j < 0) j = 0; + if (j >= p->sample_count) j = p->sample_count-1; int k = round((0.1+p->debug[j]/max)*0.8*height); - if(first) { + if (first) { cairo_move_to(c,i+.5,height-k-.5); first = 0; } else @@ -398,11 +398,11 @@ void draw_waveform( int i; // Draw vertical time lines every ms - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + for (i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); cairo_move_to(cr, x + .5, height / 2 + .5); cairo_line_to(cr, x + .5, height - .5); - if(i%5) + if (i%5) cairo_set_source(cr, grid_color); else cairo_set_source(cr, grid2_color); @@ -411,8 +411,8 @@ void draw_waveform( // Draw numbers for time scale, every 5 ms cairo_set_source(cr, text_color); - for(i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { - if(!(i%5)) { + for (i = 1-NEGATIVE_SPAN; i < POSITIVE_SPAN; i++) { + if (!(i%5)) { int x = (NEGATIVE_SPAN + i) * width / (POSITIVE_SPAN + NEGATIVE_SPAN); char s[10]; snprintf(s, 10, "%d", i); @@ -450,11 +450,11 @@ void draw_waveform( // Draw numbers for amplitude scale double last_x = 0; cairo_set_source(cr, text_color); - for(i = 50; i < 360; i+=50) { + for (i = 50; i < 360; i+=50) { double t = period*amplitude_to_time(w->la, i); - if(t > .001 * NEGATIVE_SPAN) continue; + if (t > .001 * NEGATIVE_SPAN) continue; int x = round(width * (NEGATIVE_SPAN - 1000*t) / (NEGATIVE_SPAN + POSITIVE_SPAN)); - if(x > last_x) { + if (x > last_x) { char s[10]; snprintf(s, 10, "%d", abs(i)); cairo_move_to(cr, x + fontsize/4, fontsize * 3 / 2); @@ -530,32 +530,35 @@ double get_toc_pulse(struct processing_buffers *p) /* Draw the watch icon in the info area */ gboolean icon_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w) { + // int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); + int happy = !!w->signal; // Watch hands cairo_set_line_width(cr, 5); - cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); // Rounded hands + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); // Rounded hands cairo_set_source(cr, happy ? icon1 : icon2); - cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT * 0.75, OUTPUT_WINDOW_HEIGHT * (0.75 - 0.5*happy)); - cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT * 0.35, OUTPUT_WINDOW_HEIGHT * (0.65 - 0.3*happy)); + cairo_move_to(cr, height * 0.5, height * 0.5); + cairo_line_to(cr, height * 0.75, height * (0.75 - 0.5*happy)); + cairo_move_to(cr, height * 0.5, height * 0.5); + cairo_line_to(cr, height * 0.35, height * (0.65 - 0.3*happy)); cairo_stroke(cr); // Watch outline cairo_set_line_width(cr, 6); - cairo_arc(cr, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.5, OUTPUT_WINDOW_HEIGHT * 0.4, 0, 2*M_PI); + cairo_arc(cr, height * 0.5, height * 0.5, height * 0.4, 0, 2*M_PI); cairo_stroke(cr); - const int l = OUTPUT_WINDOW_HEIGHT * 0.8 / (2*NSTEPS - 1); + const int l = height * 0.8 / (2*NSTEPS - 1); int i; cairo_set_line_width(cr, 1); - for(i = 0; i < w->signal; i++) { - cairo_move_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 1.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - (2*i+1)*l); - cairo_line_to(cr, OUTPUT_WINDOW_HEIGHT + 0.5*l, OUTPUT_WINDOW_HEIGHT * 0.9 - 2*i*l); + for (i = 0; i < w->signal; i++) { + cairo_move_to(cr, height + 0.5*l, height * 0.9 - 2*i*l); + cairo_line_to(cr, height + 1.5*l, height * 0.9 - 2*i*l); + cairo_line_to(cr, height + 1.5*l, height * 0.9 - (2*i+1)*l); + cairo_line_to(cr, height + 0.5*l, height * 0.9 - (2*i+1)*l); + cairo_line_to(cr, height + 0.5*l, height * 0.9 - 2*i*l); cairo_stroke_preserve(cr); cairo_fill(cr); } @@ -587,7 +590,7 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w double toc, a=0, b=0; - if(p) { + if (p) { toc = p->tic < p->toc ? p->toc : p->toc + p->period; a = ((double)p->tic + toc)/2 - p->period/2; b = ((double)p->tic + toc)/2 + p->period/2; @@ -608,18 +611,18 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w } int i; - for(i = 1; i < 16; i++) { + for (i = 1; i < 16; i++) { int x = i * width / 16; cairo_move_to(cr, x+.5, .5); cairo_line_to(cr, x+.5, height - .5); - if(i % 4) + if (i % 4) cairo_set_source(cr, grid_color); else cairo_set_source(cr, grid2_color); cairo_stroke(cr); } - if(p) { + if (p) { draw_graph(a,b,cr,p,w->period_drawing_area); cairo_set_source(cr, old ? stopped_color : waveform_color); @@ -646,11 +649,11 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo #else uint64_t time = timestamp; #endif - if(p && !old) { + if (p && !old) { uint64_t last = w->events[w->events_wp]; - for(i=0; ievents[i]; i++) - if(p->events[i] > last + floor(p->period / 4)) { - if(++w->events_wp == EVENTS_COUNT) w->events_wp = 0; + for (i=0; ievents[i]; i++) + if (p->events[i] > last + floor(p->period / 4)) { + if (++w->events_wp == EVENTS_COUNT) w->events_wp = 0; w->events[w->events_wp] = p->events[i]; debug("event at %llu\n", w->events[w->events_wp]); } @@ -665,7 +668,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo int height = gtk_widget_get_allocated_height(w->paperstrip_drawing_area); int stopped = 0; - if(w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { + if (w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { time = 5 * w->sample_rate + w->events[w->events_wp]; stopped = 1; } @@ -682,7 +685,7 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo for (i=0; i<4; i++) { double y = 0; cairo_move_to(cr, (double)width * (i+.5) / 4, 0); - for(;;) { + for (;;) { double x = y * slope + (double)width * (i+.5) / 4; x = fmod(x, width); if (x < 0) x += width; @@ -1046,7 +1049,7 @@ void init_main_window(struct main_window *w) // Fill in pre-defined values gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), "Automatic"); int *bph; - for(bph = preset_bph; *bph; bph++) { + for (bph = preset_bph; *bph; bph++) { char s[50]; snprintf(s, 50, "%d", *bph); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(w->bph_combo_box), s); @@ -1069,6 +1072,7 @@ void init_main_window(struct main_window *w) // Prefs button GtkWidget *prefs_button = gtk_button_new_with_label("Settings"); + gtk_widget_set_name(prefs_button, "settings_button"); // To allow for CSS styling gtk_widget_set_halign(prefs_button, GTK_ALIGN_END); // Right aligned gtk_widget_set_hexpand(prefs_button, TRUE); // Needed to make the grid expand and make room for the button at the far right g_signal_connect(prefs_button, "clicked", G_CALLBACK(show_preferences), w); @@ -1076,7 +1080,7 @@ void init_main_window(struct main_window *w) // Info grid GtkWidget *info_grid = gtk_grid_new(); // The grid containing the info text, default to horizontal orientation - gtk_grid_set_column_spacing(GTK_GRID(info_grid), 20); + gtk_grid_set_column_spacing(GTK_GRID(info_grid), 10); // Watch icon w->icon_drawing_area = gtk_drawing_area_new(); @@ -1087,24 +1091,32 @@ void init_main_window(struct main_window *w) // Rate Label w->rate_label = gtk_label_new(NULL); + gtk_label_set_width_chars(GTK_LABEL(w->rate_label), 6); + gtk_label_set_xalign(GTK_LABEL(w->rate_label), 1); // Right align gtk_label_set_markup(GTK_LABEL(w->rate_label), "---- s/d"); gtk_widget_set_name(w->rate_label, "rate"); gtk_container_add(GTK_CONTAINER(info_grid), w->rate_label); // Add to grid // Beat Error Label w->beaterror_label = gtk_label_new(NULL); + gtk_label_set_width_chars(GTK_LABEL(w->beaterror_label), 6); + gtk_label_set_xalign(GTK_LABEL(w->beaterror_label), 1); // Right align gtk_label_set_markup(GTK_LABEL(w->beaterror_label), "---- ms"); gtk_widget_set_name(w->beaterror_label, "beaterror"); gtk_container_add(GTK_CONTAINER(info_grid), w->beaterror_label); // Add to grid // Amplitude Label w->amplitude_label = gtk_label_new(NULL); + gtk_label_set_width_chars(GTK_LABEL(w->amplitude_label), 4); + gtk_label_set_xalign(GTK_LABEL(w->amplitude_label), 1); // Right align gtk_label_set_markup(GTK_LABEL(w->amplitude_label), "---˚"); gtk_widget_set_name(w->amplitude_label, "amplitude"); gtk_container_add(GTK_CONTAINER(info_grid), w->amplitude_label); // Add to grid // BPH Label w->bph_label = gtk_label_new(NULL); + gtk_label_set_width_chars(GTK_LABEL(w->bph_label), 8); + gtk_label_set_xalign(GTK_LABEL(w->bph_label), 1); // Right align gtk_label_set_markup(GTK_LABEL(w->bph_label), "21600 bph"); gtk_widget_set_name(w->bph_label, "bph"); gtk_container_add(GTK_CONTAINER(info_grid), w->bph_label); // Add to grid @@ -1192,13 +1204,12 @@ void init_main_window(struct main_window *w) gtk_container_add(GTK_CONTAINER(right_grid), w->debug_drawing_area); #endif - // Populate the root grid with the grids we created above + // Populate the root grid with the children grids we created above GtkWidget *root_grid = gtk_grid_new(); // The grid containing all of the UI gtk_orientable_set_orientation(GTK_ORIENTABLE(root_grid), GTK_ORIENTATION_VERTICAL); gtk_grid_set_row_spacing(GTK_GRID(root_grid), 5); - gtk_grid_set_column_spacing(GTK_GRID(root_grid), 10); gtk_container_add(GTK_CONTAINER(w->window), root_grid); // Add the root grid to the window - + // Add the child grids inside the root grid gtk_container_add(GTK_CONTAINER(root_grid), settings_grid); gtk_container_add(GTK_CONTAINER(root_grid), info_grid); gtk_container_add(GTK_CONTAINER(root_grid), w->panes); @@ -1206,8 +1217,7 @@ void init_main_window(struct main_window *w) // All done. Show all the widgets. gtk_widget_show_all(w->window); - // gtk_window_set_interactive_debugging(TRUE); - // gtk_window_maximize(GTK_WINDOW(w->window)); + gtk_window_set_interactive_debugging(TRUE); } /* Called when the GTK application starts running */ @@ -1224,7 +1234,7 @@ void activate (GtkApplication* app, gpointer user_data) struct processing_buffers p[NSTEPS]; int i; - for(i=0; i < NSTEPS; i++) { + for (i=0; i < NSTEPS; i++) { p[i].sample_rate = nominal_sr; p[i].sample_count = nominal_sr * (1<<(i+FIRST_STEP)); setup_buffers(&p[i]); From bef1490434f78370346e320267708e7f17b8228d Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 15 Jan 2016 03:11:52 +0100 Subject: [PATCH 16/25] Further info text improvements. --- interface.c | 93 +++++++++++++++++++++++++++++++++-------------------- tg.css | 16 +++++++++ 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/interface.c b/interface.c index da05cdb..edff418 100644 --- a/interface.c +++ b/interface.c @@ -218,41 +218,39 @@ double get_amplitude(double la, struct processing_buffers *p) void set_rate_label(struct main_window *w, int rate) { - char rate_str[20]; - char output[99]; - snprintf(rate_str, 20, "%s%d", rate > 0 ? "+" : rate < 0 ? "-" : "", abs(rate)); - snprintf(output, 99, "%4s s/d", rate_str); - gtk_label_set_markup(GTK_LABEL(w->rate_label), output); + char output[6]; + snprintf(output, 6, "%+4d", rate); + gtk_label_set_text(GTK_LABEL(w->rate_label), output); } void set_beaterror_label(struct main_window *w, double be) { - char output[99]; - snprintf(output, 99, "%4.1f ms", be); - gtk_label_set_markup(GTK_LABEL(w->beaterror_label), output); - // gtk_widget_set_tooltip_text(w->beaterror_label, "12˚"); // Alternate display + char output[8]; + snprintf(output, 8, "%4.1f", be); + gtk_label_set_text(GTK_LABEL(w->beaterror_label), output); + // gtk_widget_set_tooltip_text(w->beaterror_label, "12˚"); // Alternate unit display } void set_amplitude_label(struct main_window *w, double amp) { - char output[99]; - snprintf(output, 99, "%3.0f˚", amp); - gtk_label_set_markup(GTK_LABEL(w->amplitude_label), output); + char output[8]; + snprintf(output, 8, "%3.0f˚", amp); + gtk_label_set_text(GTK_LABEL(w->amplitude_label), output); } void set_bph_label(struct main_window *w, int bph) { - char output[99]; - snprintf(output, 99, "%d bph", bph); - gtk_label_set_markup(GTK_LABEL(w->bph_label), output); + char output[8]; + snprintf(output, 8, "%d", bph); + gtk_label_set_text(GTK_LABEL(w->bph_label), output); } #ifdef DEBUG void set_fps_label(struct main_window *w, double fps) { - char output[99]; - snprintf(output, 99, "%.2f fps", fps); - gtk_label_set_markup(GTK_LABEL(w->fps_label), output); + char output[12]; + snprintf(output, 12, "%.2f fps", fps); + gtk_label_set_text(GTK_LABEL(w->fps_label), output); } #endif @@ -1080,7 +1078,7 @@ void init_main_window(struct main_window *w) // Info grid GtkWidget *info_grid = gtk_grid_new(); // The grid containing the info text, default to horizontal orientation - gtk_grid_set_column_spacing(GTK_GRID(info_grid), 10); + gtk_grid_set_column_spacing(GTK_GRID(info_grid), 0); // Watch icon w->icon_drawing_area = gtk_drawing_area_new(); @@ -1090,36 +1088,61 @@ void init_main_window(struct main_window *w) gtk_container_add(GTK_CONTAINER(info_grid), w->icon_drawing_area); // Add to grid // Rate Label - w->rate_label = gtk_label_new(NULL); - gtk_label_set_width_chars(GTK_LABEL(w->rate_label), 6); + w->rate_label = gtk_label_new("----"); + gtk_widget_set_valign(w->rate_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(w->rate_label, 4); + gtk_label_set_width_chars(GTK_LABEL(w->rate_label), 4); gtk_label_set_xalign(GTK_LABEL(w->rate_label), 1); // Right align - gtk_label_set_markup(GTK_LABEL(w->rate_label), "---- s/d"); gtk_widget_set_name(w->rate_label, "rate"); - gtk_container_add(GTK_CONTAINER(info_grid), w->rate_label); // Add to grid + gtk_container_add(GTK_CONTAINER(info_grid), w->rate_label); + + GtkWidget *rate_unit_label = gtk_label_new("s/d"); + gtk_widget_set_valign(rate_unit_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(rate_unit_label, 10); + gtk_label_set_xalign(GTK_LABEL(rate_unit_label), 0); // Left align + gtk_widget_set_name(rate_unit_label, "rate_units"); + gtk_container_add(GTK_CONTAINER(info_grid), rate_unit_label); // Beat Error Label - w->beaterror_label = gtk_label_new(NULL); - gtk_label_set_width_chars(GTK_LABEL(w->beaterror_label), 6); + w->beaterror_label = gtk_label_new("----"); + gtk_widget_set_valign(w->beaterror_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(w->beaterror_label, 4); + gtk_label_set_width_chars(GTK_LABEL(w->beaterror_label), 3); gtk_label_set_xalign(GTK_LABEL(w->beaterror_label), 1); // Right align - gtk_label_set_markup(GTK_LABEL(w->beaterror_label), "---- ms"); gtk_widget_set_name(w->beaterror_label, "beaterror"); - gtk_container_add(GTK_CONTAINER(info_grid), w->beaterror_label); // Add to grid + gtk_container_add(GTK_CONTAINER(info_grid), w->beaterror_label); + + GtkWidget *beaterror_unit_label = gtk_label_new("ms"); + gtk_widget_set_valign(beaterror_unit_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(beaterror_unit_label, 10); + gtk_label_set_xalign(GTK_LABEL(beaterror_unit_label), 0); // Left align + gtk_widget_set_name(beaterror_unit_label, "beaterror_units"); + gtk_container_add(GTK_CONTAINER(info_grid), beaterror_unit_label); // Amplitude Label - w->amplitude_label = gtk_label_new(NULL); + w->amplitude_label = gtk_label_new("---˚"); + gtk_widget_set_valign(w->amplitude_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(w->amplitude_label, 10); gtk_label_set_width_chars(GTK_LABEL(w->amplitude_label), 4); gtk_label_set_xalign(GTK_LABEL(w->amplitude_label), 1); // Right align - gtk_label_set_markup(GTK_LABEL(w->amplitude_label), "---˚"); gtk_widget_set_name(w->amplitude_label, "amplitude"); - gtk_container_add(GTK_CONTAINER(info_grid), w->amplitude_label); // Add to grid + gtk_container_add(GTK_CONTAINER(info_grid), w->amplitude_label); // BPH Label - w->bph_label = gtk_label_new(NULL); - gtk_label_set_width_chars(GTK_LABEL(w->bph_label), 8); + w->bph_label = gtk_label_new("21600"); + gtk_widget_set_valign(w->bph_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(w->bph_label, 4); + gtk_label_set_width_chars(GTK_LABEL(w->bph_label), 5); gtk_label_set_xalign(GTK_LABEL(w->bph_label), 1); // Right align - gtk_label_set_markup(GTK_LABEL(w->bph_label), "21600 bph"); gtk_widget_set_name(w->bph_label, "bph"); - gtk_container_add(GTK_CONTAINER(info_grid), w->bph_label); // Add to grid + gtk_container_add(GTK_CONTAINER(info_grid), w->bph_label); + + GtkWidget *bph_unit_label = gtk_label_new("bph"); + gtk_widget_set_valign(bph_unit_label, GTK_ALIGN_BASELINE); + gtk_widget_set_margin_end(bph_unit_label, 10); + gtk_label_set_xalign(GTK_LABEL(bph_unit_label), 0); // Left align + gtk_widget_set_name(bph_unit_label, "bph_units"); + gtk_container_add(GTK_CONTAINER(info_grid), bph_unit_label); // Populate the panes w->panes = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); @@ -1217,7 +1240,7 @@ void init_main_window(struct main_window *w) // All done. Show all the widgets. gtk_widget_show_all(w->window); - gtk_window_set_interactive_debugging(TRUE); + // gtk_window_set_interactive_debugging(TRUE); } /* Called when the GTK application starts running */ diff --git a/tg.css b/tg.css index c8c5d0a..a960e4a 100644 --- a/tg.css +++ b/tg.css @@ -18,18 +18,34 @@ #rate { font-size: 40px; + font-weight: bold; +} + +#rate_units { + font-size: 24px; } #beaterror { font-size: 40px; + font-weight: bold; +} + +#beaterror_units { + font-size: 24px; } #amplitude { font-size: 40px; + font-weight: bold; } #bph { font-size: 40px; + font-weight: bold; +} + +#bph_units { + font-size: 24px; } #clear_button { From 834dcdeff70156da92c1420d50da6dd457cc4d82 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 15 Jan 2016 05:33:58 +0100 Subject: [PATCH 17/25] Cleanup spaces to tabs in css file. --- tg.css | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tg.css b/tg.css index a960e4a..fa8d569 100644 --- a/tg.css +++ b/tg.css @@ -13,11 +13,11 @@ * { - -GtkPaned-handle-size: 10; + -GtkPaned-handle-size: 10; } #rate { - font-size: 40px; + font-size: 40px; font-weight: bold; } @@ -26,7 +26,7 @@ } #beaterror { - font-size: 40px; + font-size: 40px; font-weight: bold; } @@ -35,12 +35,12 @@ } #amplitude { - font-size: 40px; + font-size: 40px; font-weight: bold; } #bph { - font-size: 40px; + font-size: 40px; font-weight: bold; } @@ -49,9 +49,9 @@ } #clear_button { - /* font-size: 16px; */ + /* font-size: 16px; */ } #center_button { - /* font-size: 16px; */ + /* font-size: 16px; */ } From 09af52a986671beb39c96e829c8bb77826a42289 Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 9 Feb 2016 21:13:59 +0100 Subject: [PATCH 18/25] Cancel button in settings dialog. --- interface.c | 36 +++++++++++++++--------------------- tg.css | 2 +- tg.ini | 1 - 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/interface.c b/interface.c index edff418..fcb5da0 100644 --- a/interface.c +++ b/interface.c @@ -918,6 +918,8 @@ void show_preferences(GtkButton *button, struct main_window *w) { dialog = gtk_dialog_new_with_buttons ("Settings", GTK_WINDOW(w->window), flags, + ("Cancel"), + GTK_RESPONSE_CANCEL, "OK", GTK_RESPONSE_ACCEPT, NULL); @@ -952,11 +954,6 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_widget_set_tooltip_text(dark_label, "Use a darker user interface with less light pollution."); gtk_widget_set_halign(dark_label, GTK_ALIGN_END); // Right aligned gtk_grid_attach(GTK_GRID(prefs_grid), dark_label, 0,3,1,1); - - GtkWidget *ticks_label = gtk_label_new("Clicks:"); - gtk_widget_set_tooltip_text(ticks_label, "Audible clicks for each beat detected."); - gtk_widget_set_halign(ticks_label, GTK_ALIGN_END); // Right aligned - gtk_grid_attach(GTK_GRID(prefs_grid), ticks_label, 0,4,1,1); GtkWidget *input = gtk_combo_box_text_new(); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(input), "Default"); @@ -966,7 +963,7 @@ void show_preferences(GtkButton *button, struct main_window *w) { if (strcmp(w->conf.audio_input, input_name(n)) == 0) active=n; } gtk_combo_box_set_active(GTK_COMBO_BOX(input), active); // Fill in value from settings - gtk_grid_attach_next_to(GTK_GRID(prefs_grid), input, input_label, GTK_POS_RIGHT,2,1); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), input, input_label, GTK_POS_RIGHT, 2, 1); GtkWidget *adjustment = gtk_entry_new(); gtk_entry_set_input_purpose(GTK_ENTRY(adjustment), GTK_INPUT_PURPOSE_NUMBER); @@ -980,32 +977,29 @@ void show_preferences(GtkButton *button, struct main_window *w) { gtk_grid_attach_next_to(GTK_GRID(prefs_grid), adjustment, adjust_label, GTK_POS_RIGHT, 1, 1); GtkWidget *secs = gtk_label_new("seconds/day"); - gtk_grid_attach_next_to(GTK_GRID(prefs_grid), secs, adjustment, GTK_POS_RIGHT,1,1); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), secs, adjustment, GTK_POS_RIGHT, 1, 1); GtkWidget *cpu = gtk_check_button_new(); if (w->conf.precision_mode) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cpu), TRUE); - gtk_grid_attach_next_to(GTK_GRID(prefs_grid), cpu, cpu_label, GTK_POS_RIGHT, 2,1); + gtk_grid_attach_next_to(GTK_GRID(prefs_grid), cpu, cpu_label, GTK_POS_RIGHT, 2, 1); GtkWidget *dark = gtk_check_button_new(); if (w->conf.dark_theme) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dark), TRUE); gtk_grid_attach_next_to(GTK_GRID(prefs_grid), dark, dark_label, GTK_POS_RIGHT, 2, 1); - GtkWidget *ticks = gtk_check_button_new(); - if (w->conf.ticks) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ticks), TRUE); - gtk_grid_attach_next_to(GTK_GRID(prefs_grid), ticks, ticks_label, GTK_POS_RIGHT, 2, 1); - gtk_widget_show_all(dialog); - gtk_dialog_run(GTK_DIALOG(dialog)); // Show the dialog - - // Save the dialog data in the settings variable and then save it out to disk - w->conf.audio_input = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(input)); - w->conf.rate_adjustment = atof(gtk_entry_get_text(GTK_ENTRY(adjustment))); - w->conf.precision_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cpu)); - w->conf.dark_theme = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dark)); - w->conf.ticks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ticks)); + int res = gtk_dialog_run(GTK_DIALOG(dialog)); // Show the dialog - save_settings(&w->conf); + if (res == GTK_RESPONSE_ACCEPT) { + // Save the dialog data in the settings variable and then save it out to disk + w->conf.audio_input = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(input)); + w->conf.rate_adjustment = atof(gtk_entry_get_text(GTK_ENTRY(adjustment))); + w->conf.precision_mode = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cpu)); + w->conf.dark_theme = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dark)); + + save_settings(&w->conf); + } // Get rid of the dialog gtk_widget_destroy(GTK_WIDGET(dialog)); diff --git a/tg.css b/tg.css index fa8d569..a1cb9a5 100644 --- a/tg.css +++ b/tg.css @@ -5,7 +5,7 @@ @define-color waveform_stopped rgb(255, 255, 0); /* yellow */ @define-color pulse rgb(0, 0, 100%); /* blue */ @define-color pulse_range rgb(0, 0, 50%); /* blueish */ -@define-color icon_on rgb(0, 100%, 0); /* green */ +@define-color icon_on rgb(105, 200, 61); /* green */ @define-color icon_off rgb(100%, 0, 0); /* red */ @define-color text rgb(255, 255, 255); /* white */ @define-color tick rgb(128, 180, 255); /* pale blue */ diff --git a/tg.ini b/tg.ini index 4be61b5..967d3cb 100644 --- a/tg.ini +++ b/tg.ini @@ -4,7 +4,6 @@ audio_input=Default rate_adjustment=0.0 precision_mode=true -ticks=false [ui] dark_theme=true From c51c25df64230a3804955c4e78ec69751172729d Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 9 Feb 2016 23:14:49 +0100 Subject: [PATCH 19/25] Removed unused variable. --- tg.h | 1 - 1 file changed, 1 deletion(-) diff --git a/tg.h b/tg.h index 257bfdc..97afe83 100644 --- a/tg.h +++ b/tg.h @@ -109,7 +109,6 @@ struct Settings gchar *audio_input; gdouble rate_adjustment; gboolean precision_mode; - gboolean ticks; gboolean dark_theme; int window_width, window_height, pane_pos; }; From 0fb9f6ca5325c3d8aaab708e2dd1aa4e25cdf2c2 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 10 Feb 2016 14:11:16 +0100 Subject: [PATCH 20/25] More unused variables deleted. --- prefs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/prefs.c b/prefs.c index ddae49a..cd526ce 100644 --- a/prefs.c +++ b/prefs.c @@ -39,7 +39,6 @@ void load_settings(struct Settings *conf) { conf->audio_input = g_key_file_get_string(key_file, "main", "audio_input", &err); conf->rate_adjustment = g_key_file_get_double(key_file, "main", "rate_adjustment", &err); conf->precision_mode = g_key_file_get_boolean(key_file, "main", "precision_mode", &err); - conf->ticks = g_key_file_get_boolean(key_file, "main", "ticks", &err); conf->dark_theme = g_key_file_get_boolean(key_file, "ui", "dark_theme", &err); conf->window_width = g_key_file_get_integer(key_file, "ui", "window_width", &err); conf->window_height = g_key_file_get_integer(key_file, "ui", "window_height", &err); @@ -51,7 +50,6 @@ void save_settings(struct Settings *conf) { g_key_file_set_string(key_file, "main", "audio_input", conf->audio_input); g_key_file_set_double(key_file, "main", "rate_adjustment", conf->rate_adjustment); g_key_file_set_boolean(key_file, "main", "precision_mode", conf->precision_mode); - g_key_file_set_boolean(key_file, "main", "ticks", conf->ticks); g_key_file_set_boolean(key_file, "ui", "dark_theme", conf->dark_theme); g_key_file_set_integer(key_file, "ui", "window_width", conf->window_width); g_key_file_set_integer(key_file, "ui", "window_height", conf->window_height); From a505216ddca4ecab2390353883dd5e0a60284480 Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 10 Feb 2016 17:33:11 +0100 Subject: [PATCH 21/25] Allow for decimal lift angles as requested on WUS. --- interface.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface.c b/interface.c index fcb5da0..fa13cee 100644 --- a/interface.c +++ b/interface.c @@ -845,6 +845,11 @@ void handle_la_change(GtkSpinButton *b, struct main_window *w) double la = gtk_spin_button_get_value(b); if (la < MIN_LA || la > MAX_LA) la = DEFAULT_LA; w->la = la; + if (rintf(la) == la) { // Don't display unnecessary decimals + char str[8]; + snprintf(str, 8, "%d", (int)la); + gtk_entry_set_text(GTK_ENTRY(b), str); + } redraw(w); } @@ -1058,7 +1063,7 @@ void init_main_window(struct main_window *w) // Lift angle spin button w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); // Start at default value + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w->la_spin_button), 2); // Allow for decimal lift angles to be manually entered g_signal_connect(w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); gtk_container_add(GTK_CONTAINER(settings_grid), w->la_spin_button); @@ -1234,6 +1239,9 @@ void init_main_window(struct main_window *w) // All done. Show all the widgets. gtk_widget_show_all(w->window); + // Set the lift angle spinner to the default value + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w->la_spin_button), DEFAULT_LA); + // gtk_window_set_interactive_debugging(TRUE); } From 9a2402d8d3dc0ff18e8448fd1175cd3ef052632b Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 17 Feb 2016 00:09:15 +0100 Subject: [PATCH 22/25] Screenshot feature. --- interface.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/interface.c b/interface.c index fa13cee..169f3f8 100644 --- a/interface.c +++ b/interface.c @@ -20,6 +20,10 @@ #include #include +#include +#include +#include + int preset_bph[] = PRESET_BPH; cairo_pattern_t *bg_color,*waveform_color,*grid2_color,*grid_color,*pulse_color,*range_color,*stopped_color,*icon1,*icon2,*text_color,*tick_color,*tock_color; @@ -100,6 +104,7 @@ void initialize_palette(GtkWidget *win) struct main_window { GtkWidget *window; + GtkWidget *settings_grid; GtkWidget *bph_combo_box; GtkWidget *la_spin_button; GtkWidget *icon_drawing_area; @@ -916,6 +921,106 @@ gboolean paperstrip_scroll_event(GtkWidget *widget, GdkEvent *event, struct main return TRUE; // Stop event propagation } +/* Called when file type is changed in the save screenshot dialog */ +void handle_filetype_change(GtkComboBox *format_combo, GtkFileChooser *chooser) { + // The id is known to be the file extension. + const char *the_id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(format_combo)); + + // Update filter, to only display appropriate files. + GtkFileFilter *filter = gtk_file_filter_new(); + char *pattern = g_strdup_printf("*.%s", the_id); + gtk_file_filter_add_pattern(filter, pattern); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(chooser), filter); + + // Update filename extension in the chooser + gchar *filename = gtk_file_chooser_get_filename(chooser); + if (filename != NULL) { + // strdup result from basename as we are going to modify it. + char *base = g_strdup(basename(filename)); + // Remove extension from string, if there is one. + if (strrchr(base, '.')) *(strrchr(base, '.')) = '\0'; + // Assemble and set new suggested filename + char *new_filename = g_strdup_printf("%s.%s", base, the_id); + gtk_file_chooser_set_current_name(chooser, new_filename); + + g_free(filename); + g_free(base); + g_free(new_filename); + } +} + +/* Display the save screenshot dialog */ +void save_screenshot(GtkButton *button, struct main_window *w) { + + // Set up the file chooser + GtkWidget *chooser = gtk_file_chooser_dialog_new("Save screenshot", + GTK_WINDOW(w->window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, + "Save", GTK_RESPONSE_ACCEPT, + NULL); + + // Generate file name. + GDateTime *now = g_date_time_new_now_local(); + gchar *d = g_date_time_format(now, "%F at %k.%M.%S"); + g_date_time_unref(now); + char *filename = g_strdup_printf("%s %s.pdf", g_get_application_name(), d); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), filename); + g_free(filename); + + gtk_window_set_transient_for(GTK_WINDOW(chooser), GTK_WINDOW(w->window)); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER (chooser), TRUE); + + // File type selection combo box + GtkWidget *format_combo = gtk_combo_box_text_new(); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(format_combo), "pdf", "Save as PDF (*.pdf)"); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(format_combo), "png", "Save as PNG (*.png)"); + g_signal_connect(GTK_COMBO_BOX(format_combo),"changed", G_CALLBACK(handle_filetype_change), chooser); + + // Default to PDF + gtk_combo_box_set_active_id(GTK_COMBO_BOX(format_combo), "pdf"); + + // Add file type selector in a box as the "extra" of the file chooser dialog + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); + gtk_widget_set_hexpand(box, TRUE); + gtk_widget_set_halign(format_combo, GTK_ALIGN_END); + gtk_box_pack_start(GTK_BOX(box), format_combo, FALSE, FALSE, 0); + gtk_widget_show_all(box); + gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(chooser), box); + + // Run the dialog and act if OK was pressed + int res = gtk_dialog_run(GTK_DIALOG(chooser)); + + if (res == GTK_RESPONSE_ACCEPT) { + gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)); + + GtkWidget *widget = w->window; // The source of the image we want to save + cairo_surface_t *surface; + + // Required height to crop the settings area + the borders + int cropping = gtk_widget_get_allocated_height(w->settings_grid) + gtk_container_get_border_width(GTK_CONTAINER(w->window)) + 3; + + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget) - cropping; + + int type = gtk_combo_box_get_active(GTK_COMBO_BOX(format_combo)); + if (type == 1) { // PNG + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + } else { // PDF + surface = cairo_pdf_surface_create(filename, 1.0*width, 1.0*height); + } + + cairo_t *cr = cairo_create(surface); + cairo_translate(cr, 0.0, cropping*-1.0); // Crop top (settings area) + gtk_widget_draw(widget, cr); + cairo_destroy(cr); + if (type == 1) cairo_surface_write_to_png(surface, filename); + cairo_surface_destroy(surface); + } + + gtk_widget_destroy(chooser); +} + /* Display the Settings dialog */ void show_preferences(GtkButton *button, struct main_window *w) { GtkWidget *dialog; @@ -1007,7 +1112,7 @@ void show_preferences(GtkButton *button, struct main_window *w) { } // Get rid of the dialog - gtk_widget_destroy(GTK_WIDGET(dialog)); + gtk_widget_destroy(dialog); } /* Set up the main window and populate with widgets */ @@ -1033,12 +1138,12 @@ void init_main_window(struct main_window *w) gtk_window_set_title(GTK_WINDOW(w->window), PROGRAM_NAME " " VERSION); // Populate the settings grid - GtkWidget *settings_grid = gtk_grid_new(); // The grid containing the settings, default to horizontal orientation - gtk_grid_set_column_spacing(GTK_GRID(settings_grid), 2); + w->settings_grid = gtk_grid_new(); // The grid containing the settings, default to horizontal orientation + gtk_grid_set_column_spacing(GTK_GRID(w->settings_grid), 2); // Beat mode Label GtkWidget *bphmode_label = gtk_label_new("Beat mode"); - gtk_container_add(GTK_CONTAINER(settings_grid), bphmode_label); // Add to grid + gtk_container_add(GTK_CONTAINER(w->settings_grid), bphmode_label); // Add to grid // BPH combo box w->bph_combo_box = gtk_combo_box_text_new_with_entry(); @@ -1054,26 +1159,34 @@ void init_main_window(struct main_window *w) gtk_combo_box_set_active(GTK_COMBO_BOX(w->bph_combo_box), 0); gtk_widget_set_can_default(w->bph_combo_box, FALSE); // Try to avoid getting the automatic focus. Not working.... g_signal_connect(w->bph_combo_box, "changed", G_CALLBACK(handle_bph_change), w); - gtk_container_add(GTK_CONTAINER(settings_grid), w->bph_combo_box); + gtk_container_add(GTK_CONTAINER(w->settings_grid), w->bph_combo_box); // Lift angle label GtkWidget *la_label = gtk_label_new("Lift angle"); gtk_widget_set_margin_start(la_label, 10); // Make space from the widget in front - gtk_container_add(GTK_CONTAINER(settings_grid), la_label); + gtk_container_add(GTK_CONTAINER(w->settings_grid), la_label); // Lift angle spin button w->la_spin_button = gtk_spin_button_new_with_range(MIN_LA, MAX_LA, 1); gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w->la_spin_button), 2); // Allow for decimal lift angles to be manually entered g_signal_connect(w->la_spin_button, "value_changed", G_CALLBACK(handle_la_change), w); - gtk_container_add(GTK_CONTAINER(settings_grid), w->la_spin_button); + gtk_container_add(GTK_CONTAINER(w->settings_grid), w->la_spin_button); + + // Screenshot button + GtkWidget *screenshot_button = gtk_button_new_with_label("Screenshot"); + gtk_widget_set_name(screenshot_button, "screenshot_button"); // To allow for CSS styling + gtk_widget_set_halign(screenshot_button, GTK_ALIGN_END); // Right aligned + gtk_widget_set_hexpand(screenshot_button, TRUE); // Needed to make the grid expand and make room for the button at the far right + g_signal_connect(screenshot_button, "clicked", G_CALLBACK(save_screenshot), w); + gtk_container_add(GTK_CONTAINER(w->settings_grid), screenshot_button); // Prefs button GtkWidget *prefs_button = gtk_button_new_with_label("Settings"); gtk_widget_set_name(prefs_button, "settings_button"); // To allow for CSS styling gtk_widget_set_halign(prefs_button, GTK_ALIGN_END); // Right aligned - gtk_widget_set_hexpand(prefs_button, TRUE); // Needed to make the grid expand and make room for the button at the far right + gtk_widget_set_hexpand(prefs_button, FALSE); g_signal_connect(prefs_button, "clicked", G_CALLBACK(show_preferences), w); - gtk_container_add(GTK_CONTAINER(settings_grid), prefs_button); + gtk_container_add(GTK_CONTAINER(w->settings_grid), prefs_button); // Info grid GtkWidget *info_grid = gtk_grid_new(); // The grid containing the info text, default to horizontal orientation @@ -1232,7 +1345,7 @@ void init_main_window(struct main_window *w) gtk_grid_set_row_spacing(GTK_GRID(root_grid), 5); gtk_container_add(GTK_CONTAINER(w->window), root_grid); // Add the root grid to the window // Add the child grids inside the root grid - gtk_container_add(GTK_CONTAINER(root_grid), settings_grid); + gtk_container_add(GTK_CONTAINER(root_grid), w->settings_grid); gtk_container_add(GTK_CONTAINER(root_grid), info_grid); gtk_container_add(GTK_CONTAINER(root_grid), w->panes); @@ -1270,6 +1383,7 @@ void activate (GtkApplication* app, gpointer user_data) w.bfs = p; w.old = NULL; w.window = gtk_application_window_new(app); + gtk_window_set_default_size(GTK_WINDOW(w.window), w.conf.window_width, w.conf.window_height); if (w.conf.dark_theme) // Use the dark theme From 9dccd27e438f372281f6dbcea71b0ae815b99acd Mon Sep 17 00:00:00 2001 From: Rob Date: Wed, 17 Feb 2016 01:18:07 +0100 Subject: [PATCH 23/25] Date formatting tweak in screenshot filenames. --- interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface.c b/interface.c index 169f3f8..9f92014 100644 --- a/interface.c +++ b/interface.c @@ -962,7 +962,7 @@ void save_screenshot(GtkButton *button, struct main_window *w) { // Generate file name. GDateTime *now = g_date_time_new_now_local(); - gchar *d = g_date_time_format(now, "%F at %k.%M.%S"); + gchar *d = g_date_time_format(now, "%F at %H.%M.%S"); g_date_time_unref(now); char *filename = g_strdup_printf("%s %s.pdf", g_get_application_name(), d); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), filename); From 47d72e512df766cb7487d4487227f03c48debbb3 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 19 Feb 2016 20:10:05 +0100 Subject: [PATCH 24/25] Whitespace fixes. --- interface.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/interface.c b/interface.c index 9f92014..5f5ce3e 100644 --- a/interface.c +++ b/interface.c @@ -951,7 +951,6 @@ void handle_filetype_change(GtkComboBox *format_combo, GtkFileChooser *chooser) /* Display the save screenshot dialog */ void save_screenshot(GtkButton *button, struct main_window *w) { - // Set up the file chooser GtkWidget *chooser = gtk_file_chooser_dialog_new("Save screenshot", GTK_WINDOW(w->window), @@ -959,17 +958,16 @@ void save_screenshot(GtkButton *button, struct main_window *w) { "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); - // Generate file name. GDateTime *now = g_date_time_new_now_local(); gchar *d = g_date_time_format(now, "%F at %H.%M.%S"); g_date_time_unref(now); char *filename = g_strdup_printf("%s %s.pdf", g_get_application_name(), d); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), filename); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), filename); g_free(filename); gtk_window_set_transient_for(GTK_WINDOW(chooser), GTK_WINDOW(w->window)); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER (chooser), TRUE); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE); // File type selection combo box GtkWidget *format_combo = gtk_combo_box_text_new(); @@ -1023,15 +1021,12 @@ void save_screenshot(GtkButton *button, struct main_window *w) { /* Display the Settings dialog */ void show_preferences(GtkButton *button, struct main_window *w) { - GtkWidget *dialog; GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; - dialog = gtk_dialog_new_with_buttons ("Settings", + GtkWidget *dialog = gtk_dialog_new_with_buttons("Settings", GTK_WINDOW(w->window), flags, - ("Cancel"), - GTK_RESPONSE_CANCEL, - "OK", - GTK_RESPONSE_ACCEPT, + ("Cancel"), GTK_RESPONSE_CANCEL, + "OK", GTK_RESPONSE_ACCEPT, NULL); // gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE); // Non-resizable From d6fc45da01c527cc2d1f844e9643ee6cc93b6d69 Mon Sep 17 00:00:00 2001 From: Rob Date: Sat, 20 Feb 2016 00:09:26 +0100 Subject: [PATCH 25/25] Cleaner screenshots by hiding the buttons below the paperstrip. --- interface.c | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/interface.c b/interface.c index 5f5ce3e..14a1661 100644 --- a/interface.c +++ b/interface.c @@ -117,6 +117,8 @@ struct main_window { GtkWidget *toc_drawing_area; GtkWidget *period_drawing_area; GtkWidget *paperstrip_drawing_area; + GtkWidget *clear_button; + GtkWidget *center_button; #ifdef DEBUG GtkWidget *fps_label; GtkWidget *debug_drawing_area; @@ -585,8 +587,8 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w { cairo_init(cr); - int width = gtk_widget_get_allocated_width(w->period_drawing_area); - int height = gtk_widget_get_allocated_height(w->period_drawing_area); + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); int old; struct processing_buffers *p = get_data(w, &old); @@ -626,7 +628,7 @@ gboolean period_draw_event(GtkWidget *widget, cairo_t *cr, struct main_window *w } if (p) { - draw_graph(a,b,cr,p,w->period_drawing_area); + draw_graph(a,b,cr,p,widget); cairo_set_source(cr, old ? stopped_color : waveform_color); cairo_stroke_preserve(cr); @@ -667,8 +669,8 @@ gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *cr, struct main_windo cairo_init(cr); - int width = gtk_widget_get_allocated_width(w->paperstrip_drawing_area); - int height = gtk_widget_get_allocated_height(w->paperstrip_drawing_area); + int width = gtk_widget_get_allocated_width(widget); + int height = gtk_widget_get_allocated_height(widget); int stopped = 0; if (w->events[w->events_wp] && time > 5 * w->sample_rate + w->events[w->events_wp]) { @@ -951,6 +953,10 @@ void handle_filetype_change(GtkComboBox *format_combo, GtkFileChooser *chooser) /* Display the save screenshot dialog */ void save_screenshot(GtkButton *button, struct main_window *w) { + // Hide Clear & Center buttons + gtk_widget_hide(w->clear_button); + gtk_widget_hide(w->center_button); + // Set up the file chooser GtkWidget *chooser = gtk_file_chooser_dialog_new("Save screenshot", GTK_WINDOW(w->window), @@ -1016,6 +1022,10 @@ void save_screenshot(GtkButton *button, struct main_window *w) { cairo_surface_destroy(surface); } + // Bring back the hidden buttons + gtk_widget_show(w->clear_button); + gtk_widget_show(w->center_button); + // Remove the save dialog gtk_widget_destroy(chooser); } @@ -1287,18 +1297,18 @@ void init_main_window(struct main_window *w) gtk_grid_attach(GTK_GRID(left_grid), w->paperstrip_drawing_area, 0,0,2,1); // CLEAR button - GtkWidget *clear_button = gtk_button_new_with_label("Clear"); - gtk_widget_set_name(clear_button, "clear_button"); // To allow for CSS styling - gtk_container_set_border_width(GTK_CONTAINER(clear_button), 2); - g_signal_connect(clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); - gtk_grid_attach(GTK_GRID(left_grid), clear_button, 0,1,1,1); + w->clear_button = gtk_button_new_with_label("Clear"); + gtk_widget_set_name(w->clear_button, "clear_button"); // To allow for CSS styling + gtk_container_set_border_width(GTK_CONTAINER(w->clear_button), 2); + g_signal_connect(w->clear_button, "clicked", G_CALLBACK(handle_clear_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), w->clear_button, 0,1,1,1); // CENTER button - GtkWidget *center_button = gtk_button_new_with_label("Center"); - gtk_widget_set_name(center_button, "center_button"); // To allow for CSS styling - gtk_container_set_border_width(GTK_CONTAINER(center_button), 2); - g_signal_connect(center_button, "clicked", G_CALLBACK(handle_center_trace), w); - gtk_grid_attach(GTK_GRID(left_grid), center_button, 1,1,1,1); + w->center_button = gtk_button_new_with_label("Center"); + gtk_widget_set_name(w->center_button, "center_button"); // To allow for CSS styling + gtk_container_set_border_width(GTK_CONTAINER(w->center_button), 2); + g_signal_connect(w->center_button, "clicked", G_CALLBACK(handle_center_trace), w); + gtk_grid_attach(GTK_GRID(left_grid), w->center_button, 1,1,1,1); // Tic waveform area w->tic_drawing_area = gtk_drawing_area_new();