diff --git a/src/config.c b/src/config.c index 18d1ee9..220b294 100644 --- a/src/config.c +++ b/src/config.c @@ -44,6 +44,8 @@ g_key_file_save_to_file (GKeyFile *key_file, #define g_key_file_get_int g_key_file_get_integer #define g_key_file_set_int g_key_file_set_integer // the devil may take glib +#define g_key_file_get_bool g_key_file_get_boolean +#define g_key_file_set_bool g_key_file_set_boolean void load_config(struct main_window *w) { diff --git a/src/interface.c b/src/interface.c index 7c1d3ca..9ae4cb7 100644 --- a/src/interface.c +++ b/src/interface.c @@ -422,7 +422,7 @@ static GtkWidget *make_tab_label(char *name, struct output_panel *panel_to_close static void add_new_tab(struct snapshot *s, char *name, struct main_window *w) { - struct output_panel *op = init_output_panel(NULL, s, 5); + struct output_panel *op = init_output_panel(NULL, s, 5, w->vertical_layout); GtkWidget *label = make_tab_label(name, op); gtk_widget_show_all(op->panel); @@ -694,6 +694,45 @@ static void load(GtkMenuItem *m, struct main_window *w) gtk_widget_destroy(dialog); } +static void handle_layout(GtkCheckMenuItem *b, struct main_window *w) +{ + const bool vertical = gtk_check_menu_item_get_active(b) == TRUE; + + w->vertical_layout = vertical; + set_panel_layout(w->active_panel, vertical); + + int n = 0; + GtkWidget *panel; + while ((panel = gtk_notebook_get_nth_page(GTK_NOTEBOOK(w->notebook), n++))) { + struct output_panel *op = g_object_get_data(G_OBJECT(panel), "op-pointer"); + if(op) + set_panel_layout(op, vertical); + } +} + +/* Add a checkbox with name to the given menu, with initial state active and + * attach the supplied callback and parameter to the toggled signal. Set is set + * before attaching the signal, so the callback is not called when created. */ +static GtkWidget* add_checkbox(GtkWidget* menu, const char* name, bool active, GCallback callback, void* param) +{ + GtkWidget *checkbox = gtk_check_menu_item_new_with_label(name); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(checkbox), active); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), checkbox); + g_signal_connect(checkbox, "toggled", callback, param); + return checkbox; +} + +/* Add a menu item with given label to the given menu, with the supplied initial +* sensitivity, callback, and callback parameter. */ +static GtkWidget* add_menu_item(GtkWidget* menu, const char* label, bool sensitive, GCallback callback, void* param) +{ + GtkWidget *item = gtk_menu_item_new_with_label(label); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_set_sensitive(item, sensitive); + g_signal_connect(item, "activate", callback, param); + return item; +} + /* Set up the main window and populate with widgets */ static void init_main_window(struct main_window *w) { @@ -800,47 +839,32 @@ static void init_main_window(struct main_window *w) gtk_box_pack_end(GTK_BOX(hbox), command_menu_button, FALSE, FALSE, 0); // ... Open - GtkWidget *open_item = gtk_menu_item_new_with_label("Open"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), open_item); - g_signal_connect(open_item, "activate", G_CALLBACK(load), w); + add_menu_item(command_menu, "Open", true, G_CALLBACK(load), w); // ... Save - w->save_item = gtk_menu_item_new_with_label("Save current display"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), w->save_item); - g_signal_connect(w->save_item, "activate", G_CALLBACK(save_current), w); - gtk_widget_set_sensitive(w->save_item, FALSE); + w->save_item = add_menu_item(command_menu, "Save current display", false, G_CALLBACK(save_current), w); // ... Save all - w->save_all_item = gtk_menu_item_new_with_label("Save all snapshots"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), w->save_all_item); - g_signal_connect(w->save_all_item, "activate", G_CALLBACK(save_all), w); - gtk_widget_set_sensitive(w->save_all_item, FALSE); + w->save_all_item = add_menu_item(command_menu, "Save all snapshots", false, G_CALLBACK(save_all), w); gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), gtk_separator_menu_item_new()); // ... Light checkbox - GtkWidget *light_checkbox = gtk_check_menu_item_new_with_label("Light algorithm"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), light_checkbox); - g_signal_connect(light_checkbox, "toggled", G_CALLBACK(handle_light), w); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(light_checkbox), w->is_light); + add_checkbox(command_menu, "Light algorithm", w->is_light, G_CALLBACK(handle_light), w); // ... Calibrate checkbox - w->cal_button = gtk_check_menu_item_new_with_label("Calibrate"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), w->cal_button); - g_signal_connect(w->cal_button, "toggled", G_CALLBACK(handle_calibrate), w); + w->cal_button = add_checkbox(command_menu, "Calibrate", false, G_CALLBACK(handle_calibrate), w); + + // Layout checkbox + add_checkbox(command_menu, "Vertical", w->vertical_layout, G_CALLBACK(handle_layout), w); gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), gtk_separator_menu_item_new()); // ... Close all - w->close_all_item = gtk_menu_item_new_with_label("Close all snapshots"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), w->close_all_item); - g_signal_connect(w->close_all_item, "activate", G_CALLBACK(close_all), w); - gtk_widget_set_sensitive(w->close_all_item, FALSE); + w->close_all_item = add_menu_item(command_menu, "Close all snapshots", false, G_CALLBACK(close_all), w); // ... Quit - GtkWidget *quit_item = gtk_menu_item_new_with_label("Quit"); - gtk_menu_shell_append(GTK_MENU_SHELL(command_menu), quit_item); - g_signal_connect(quit_item, "activate", G_CALLBACK(handle_quit), w); + add_menu_item(command_menu, "Quit", true, G_CALLBACK(handle_quit), w); gtk_widget_show_all(command_menu); @@ -931,6 +955,7 @@ static void start_interface(GApplication* app, void *p) w->la = DEFAULT_LA; w->calibrate = 0; w->is_light = 0; + w->vertical_layout = true; load_config(w); @@ -954,7 +979,7 @@ static void start_interface(GApplication* app, void *p) w->computer->curr = NULL; compute_results(w->active_snapshot); - w->active_panel = init_output_panel(w->computer, w->active_snapshot, 0); + w->active_panel = init_output_panel(w->computer, w->active_snapshot, 0, w->vertical_layout); init_main_window(w); diff --git a/src/output_panel.c b/src/output_panel.c index 0e7fc8b..c34b597 100644 --- a/src/output_panel.c +++ b/src/output_panel.c @@ -322,10 +322,9 @@ static void expose_waveform( int width = temp.width; int height = temp.height; - gtk_widget_get_allocation(gtk_widget_get_toplevel(da), &temp); - int font = temp.width / 90; - if(font < 12) - font = 12; + int font = width / 25; + font = font < 12 ? 12 : font > 24 ? 24 : font; + int i; cairo_set_font_size(c,font); @@ -549,9 +548,19 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp GtkAllocation temp; gtk_widget_get_allocation (op->paperstrip_drawing_area, &temp); + int width, height; - int width = temp.width; - int height = temp.height; + /* The paperstrip is coded to be vertical; horizontal uses cairo to rotate it. */ + if(op->vertical_layout) { + width = temp.width; + height = temp.height; + } else { + width = temp.height; + height = temp.width; + + cairo_translate(c, height, 0); + cairo_rotate(c, M_PI/2); + } int stopped = 0; if( snst->events_count && @@ -660,18 +669,14 @@ static gboolean paperstrip_draw_event(GtkWidget *widget, cairo_t *c, struct outp 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(gtk_widget_get_toplevel(widget), &temp); - int font = temp.width / 90; - if(font < 12) - font = 12; - cairo_set_font_size(c,font); + int font = width / 25; + cairo_set_font_size(c, font < 12 ? 12 : font > 24 ? 24 : font); - sprintf(s, "%.1f ms", snst->calibrate ? + char s[32]; + snprintf(s, sizeof(s), "%.1f ms", snst->calibrate ? 1000. / zoom_factor : 3600000. / (snst->guessed_bph * zoom_factor)); + cairo_text_extents_t extents; cairo_text_extents(c,s,&extents); cairo_move_to(c, (width - extents.x_advance)/2, height - 30); cairo_show_text(c,s); @@ -782,89 +787,172 @@ void op_destroy(struct output_panel *op) free(op); } -struct output_panel *init_output_panel(struct computer *comp, struct snapshot *snst, int border) +/* Creates the paperstrip, with buttons. Returns top level Widget that contains + * them. Vertical controls orientation of paper strip. */ +static GtkWidget* create_paperstrip(struct output_panel *op, bool vertical) { - struct output_panel *op = malloc(sizeof(struct output_panel)); - - op->computer = comp; - op->snst = snst; - - op->panel = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - gtk_container_set_border_width(GTK_CONTAINER(op->panel), border); - - // Info area on top - op->output_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(op->output_drawing_area, 0, OUTPUT_WINDOW_HEIGHT); - gtk_box_pack_start(GTK_BOX(op->panel),op->output_drawing_area, FALSE, TRUE, 0); - g_signal_connect (op->output_drawing_area, "draw", G_CALLBACK(output_draw_event), op); - gtk_widget_set_events(op->output_drawing_area, GDK_EXPOSURE_MASK); - - GtkWidget *hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); - gtk_box_pack_start(GTK_BOX(op->panel), hbox2, TRUE, TRUE, 0); - - GtkWidget *vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - gtk_box_pack_start(GTK_BOX(hbox2), vbox2, FALSE, TRUE, 0); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); // Paperstrip op->paperstrip_drawing_area = gtk_drawing_area_new(); - gtk_widget_set_size_request(op->paperstrip_drawing_area, 300, 0); - gtk_box_pack_start(GTK_BOX(vbox2), op->paperstrip_drawing_area, TRUE, TRUE, 0); + gtk_widget_set_size_request(op->paperstrip_drawing_area, 150, 150); + gtk_box_pack_start(GTK_BOX(vbox), op->paperstrip_drawing_area, TRUE, TRUE, 0); g_signal_connect (op->paperstrip_drawing_area, "draw", G_CALLBACK(paperstrip_draw_event), op); gtk_widget_set_events(op->paperstrip_drawing_area, GDK_EXPOSURE_MASK); - GtkWidget *hbox3 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); - gtk_box_pack_start(GTK_BOX(vbox2), hbox3, FALSE, TRUE, 0); + // Buttons + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); // < button - GtkWidget *left_button = gtk_button_new_with_label("<"); - gtk_box_pack_start(GTK_BOX(hbox3), left_button, TRUE, TRUE, 0); - g_signal_connect (left_button, "clicked", G_CALLBACK(handle_left), op); + op->left_button = gtk_button_new_from_icon_name( + vertical ? "pan-start-symbolic" : "pan-up-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_box_pack_start(GTK_BOX(hbox), op->left_button, TRUE, TRUE, 0); + g_signal_connect (op->left_button, "clicked", G_CALLBACK(handle_left), op); // CLEAR button - if(comp) { + if(op->computer) { op->clear_button = gtk_button_new_with_label("Clear"); - gtk_box_pack_start(GTK_BOX(hbox3), op->clear_button, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), op->clear_button, TRUE, TRUE, 0); g_signal_connect (op->clear_button, "clicked", G_CALLBACK(handle_clear_trace), op); - gtk_widget_set_sensitive(op->clear_button, !snst->calibrate); + gtk_widget_set_sensitive(op->clear_button, !op->snst->calibrate); } // CENTER button GtkWidget *center_button = gtk_button_new_with_label("Center"); - gtk_box_pack_start(GTK_BOX(hbox3), center_button, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), center_button, TRUE, TRUE, 0); g_signal_connect (center_button, "clicked", G_CALLBACK(handle_center_trace), op); // > button - GtkWidget *right_button = gtk_button_new_with_label(">"); - gtk_box_pack_start(GTK_BOX(hbox3), right_button, TRUE, TRUE, 0); - g_signal_connect (right_button, "clicked", G_CALLBACK(handle_right), op); + op->right_button = gtk_button_new_from_icon_name( + vertical ? "pan-end-symbolic" : "pan-down-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_box_pack_start(GTK_BOX(hbox), op->right_button, TRUE, TRUE, 0); + g_signal_connect (op->right_button, "clicked", G_CALLBACK(handle_right), op); - GtkWidget *vbox3 = gtk_box_new(GTK_ORIENTATION_VERTICAL,10); - gtk_box_pack_start(GTK_BOX(hbox2), vbox3, TRUE, TRUE, 0); + return vbox; +} + +/* Create the tic, toc, and period waveforms. Returns the GtkBox that contains + * them. Vertical controls how the waves are stacked. */ +static GtkWidget* create_waveforms(struct output_panel *op, bool vertical) +{ + GtkWidget *box = gtk_box_new(vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL, 10); // Tic waveform area op->tic_drawing_area = gtk_drawing_area_new(); - gtk_box_pack_start(GTK_BOX(vbox3), op->tic_drawing_area, TRUE, TRUE, 0); + gtk_widget_set_size_request(op->tic_drawing_area, 300, 150); + gtk_box_pack_start(GTK_BOX(box), op->tic_drawing_area, TRUE, TRUE, 0); g_signal_connect (op->tic_drawing_area, "draw", G_CALLBACK(tic_draw_event), op); gtk_widget_set_events(op->tic_drawing_area, GDK_EXPOSURE_MASK); // Toc waveform area op->toc_drawing_area = gtk_drawing_area_new(); - gtk_box_pack_start(GTK_BOX(vbox3), op->toc_drawing_area, TRUE, TRUE, 0); + gtk_widget_set_size_request(op->toc_drawing_area, 300, 150); + gtk_box_pack_start(GTK_BOX(box), op->toc_drawing_area, TRUE, TRUE, 0); g_signal_connect (op->toc_drawing_area, "draw", G_CALLBACK(toc_draw_event), op); gtk_widget_set_events(op->toc_drawing_area, GDK_EXPOSURE_MASK); // Period waveform area op->period_drawing_area = gtk_drawing_area_new(); - gtk_box_pack_start(GTK_BOX(vbox3), op->period_drawing_area, TRUE, TRUE, 0); + gtk_widget_set_size_request(op->period_drawing_area, 300, 150); + gtk_box_pack_start(GTK_BOX(box), op->period_drawing_area, TRUE, TRUE, 0); g_signal_connect (op->period_drawing_area, "draw", G_CALLBACK(period_draw_event), op); gtk_widget_set_events(op->period_drawing_area, GDK_EXPOSURE_MASK); #ifdef DEBUG op->debug_drawing_area = gtk_drawing_area_new(); - gtk_box_pack_start(GTK_BOX(vbox3), op->debug_drawing_area, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(box), op->debug_drawing_area, TRUE, TRUE, 0); g_signal_connect (op->debug_drawing_area, "draw", G_CALLBACK(debug_draw_event), op); gtk_widget_set_events(op->debug_drawing_area, GDK_EXPOSURE_MASK); #endif + return box; +} + +/* Create container and place paperstrip and waveforms in either vertical or + * horizontal paperstrip orientation. Puts container in the panel and shows it. */ +static void place_displays(struct output_panel *op, GtkWidget *paperstrip, GtkWidget *waveforms, bool vertical) +{ + op->vertical_layout = vertical; + + op->displays = gtk_paned_new(vertical ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); + gtk_paned_set_wide_handle(GTK_PANED(op->displays), TRUE); + + gtk_paned_pack1(GTK_PANED(op->displays), paperstrip, vertical ? FALSE : TRUE, FALSE); + + gtk_orientable_set_orientation(GTK_ORIENTABLE(waveforms), vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL); + gtk_paned_pack2(GTK_PANED(op->displays), waveforms, TRUE, FALSE); + + /* Make paperstrip arrows buttons point correct way */ + GtkWidget *left_arrow = gtk_button_get_image(GTK_BUTTON(op->left_button)); + gtk_image_set_from_icon_name(GTK_IMAGE(left_arrow), + vertical ? "pan-start-symbolic" : "pan-up-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + GtkWidget *right_arrow = gtk_button_get_image(GTK_BUTTON(op->right_button)); + gtk_image_set_from_icon_name(GTK_IMAGE(right_arrow), + vertical ? "pan-end-symbolic" : "pan-down-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + + gtk_box_pack_end(GTK_BOX(op->panel), op->displays, TRUE, TRUE, 0); + gtk_widget_show(op->displays); +} + +/* Create the paperstrip and waveforms, a container for them, and place it into + * the panel. Returns containing Widget. Vertical controls paperstrip + * orientation. */ +static GtkWidget *create_displays(struct output_panel *op, bool vertical) +{ + // The paperstrip and buttons + op->paperstrip_box = create_paperstrip(op, vertical); + // Tic/toc/period waveform area + op->waveforms_box = create_waveforms(op, vertical); + + place_displays(op, op->paperstrip_box, op->waveforms_box, vertical); + + return op->displays; +} + +/* Change orientation of existing output panel. Is a no-op if orientation is + * not changed. */ +void set_panel_layout(struct output_panel *op, bool vertical) +{ + if (op->vertical_layout == vertical) + return; + + /* Remove waveforms and paperstrip containers from displays container, + * then use place_displays() to put them into a new displays container. + * The need to be refed so they are not deleted when removed from the + * container. */ + g_object_ref(op->waveforms_box); + gtk_container_remove(GTK_CONTAINER(op->displays), op->waveforms_box); + + g_object_ref(op->paperstrip_box); + gtk_container_remove(GTK_CONTAINER(op->displays), op->paperstrip_box); + + gtk_widget_destroy(op->displays); op->displays = NULL; + place_displays(op, op->paperstrip_box, op->waveforms_box, vertical); + + /* They are now refed by op->displays so we don't need our refs anymore */ + g_object_unref(op->paperstrip_box); + g_object_unref(op->waveforms_box); +} + +struct output_panel *init_output_panel(struct computer *comp, struct snapshot *snst, int border, bool vertical) +{ + struct output_panel *op = malloc(sizeof(struct output_panel)); + + op->computer = comp; + op->snst = snst; + + op->panel = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_container_set_border_width(GTK_CONTAINER(op->panel), border); + + // Info area on top + op->output_drawing_area = gtk_drawing_area_new(); + gtk_widget_set_size_request(op->output_drawing_area, 0, OUTPUT_WINDOW_HEIGHT); + gtk_box_pack_start(GTK_BOX(op->panel),op->output_drawing_area, FALSE, TRUE, 0); + g_signal_connect (op->output_drawing_area, "draw", G_CALLBACK(output_draw_event), op); + gtk_widget_set_events(op->output_drawing_area, GDK_EXPOSURE_MASK); + + create_displays(op, vertical); + return op; } diff --git a/src/tg.h b/src/tg.h index 789f66c..f92d640 100644 --- a/src/tg.h +++ b/src/tg.h @@ -200,20 +200,29 @@ struct output_panel { GtkWidget *panel; GtkWidget *output_drawing_area; + GtkWidget *displays; + GtkWidget *waveforms_box; GtkWidget *tic_drawing_area; GtkWidget *toc_drawing_area; GtkWidget *period_drawing_area; + GtkWidget *paperstrip_box; GtkWidget *paperstrip_drawing_area; GtkWidget *clear_button; + GtkWidget *left_button; + GtkWidget *right_button; + GtkWidget *zoom_button; #ifdef DEBUG GtkWidget *debug_drawing_area; #endif + bool vertical_layout; + struct computer *computer; struct snapshot *snst; }; void initialize_palette(); -struct output_panel *init_output_panel(struct computer *comp, struct snapshot *snst, int border); +struct output_panel *init_output_panel(struct computer *comp, struct snapshot *snst, int border, bool vertical_layout); +void set_panel_layout(struct output_panel *op, bool vertical); void redraw_op(struct output_panel *op); void op_set_snapshot(struct output_panel *op, struct snapshot *snst); void op_set_border(struct output_panel *op, int i); @@ -250,6 +259,8 @@ struct main_window { int cal; // 0.1 s/d int nominal_sr; + bool vertical_layout; + GKeyFile *config_file; gchar *config_file_name; struct conf_data *conf_data; @@ -272,7 +283,8 @@ void error(char *format,...); OP(bph, bph, int) \ OP(lift_angle, la, double) \ OP(calibration, cal, int) \ - OP(light_algorithm, is_light, int) + OP(light_algorithm, is_light, int) \ + OP(vertical_paperstrip, vertical_layout, bool) struct conf_data { #define DEF(NAME,PLACE,TYPE) TYPE PLACE;