/*************************************************************************** * Copyright (C) 04/2008 by Olaf Rempel * * razzor@kopf-tisch.de * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; version 2 of the License * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "tdc_variable.h" #include "gui_graph_tab.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define HISTORY 1000 static unsigned int used_colors; static GdkColor colors[] = { { .red = 0x0000, .green = 0x0000, .blue = 0xFF00, }, { .red = 0xFF00, .green = 0x0000, .blue = 0x0000, }, { .red = 0x0000, .green = 0xDF00, .blue = 0x0000, }, { .red = 0x0000, .green = 0xFF00, .blue = 0xFF00, }, { .red = 0xFF00, .green = 0x0000, .blue = 0xFF00, }, { .red = 0xA500, .green = 0x2A00, .blue = 0x2A00, }, { .red = 0xFF00, .green = 0xA500, .blue = 0x0000, }, { .red = 0x8000, .green = 0x8000, .blue = 0x8000, }, }; #define MOD_CONTINUOUS 0x0001 #define MOD_SINGLESHOT 0x0002 #define MOD_RUNNING 0x0004 #define MOD_STOPPED 0x0008 static int graph_mode = MOD_CONTINUOUS | MOD_RUNNING; static struct timeval base; static GtkWidget *box; static GtkWidget *legend; static GtkWidget *yscale_min, *yscale_max; static GtkWidget *start_button, *mode_button; static GList *graphlist; struct xygraph { GtkDataboxGraph *graph; struct tdc_var *var; gfloat xarr[HISTORY]; gfloat yarr[HISTORY]; GdkColor *color; int last_index; }; static GdkColor * get_color(void) { int i; for (i = 0; i < ARRAY_SIZE(colors); i++) { if (used_colors & (1 << i)) continue; used_colors |= (1 << i); return colors +i; } return NULL; } static void free_color(GdkColor *color) { int i; for (i = 0; i < ARRAY_SIZE(colors); i++) if (color == colors +i) used_colors &= ~(1 << i); } static void update_legend_cb(gpointer data, gpointer user_data) { struct xygraph *graph = (struct xygraph *)data; GString *tmp = (GString *)user_data; g_string_append_printf(tmp,"%s\n", (graph->color->red >> 8) & 0xFF, (graph->color->green >> 8) & 0xFF, (graph->color->blue >> 8) & 0xFF, graph->var->name); } static void update_legend(void) { GString *tmp; tmp = g_string_new(""); g_list_foreach(graphlist, &update_legend_cb, tmp); /* strip last \n */ tmp->str[tmp->len -1] = 0x00; gtk_label_set_markup(GTK_LABEL(legend), tmp->str); g_string_free(tmp, TRUE); } static void rescale_graphs(gboolean reset) { static GtkDataboxValue min; static GtkDataboxValue max; GtkDataboxValue tmp_min, tmp_max; if (gtk_databox_calculate_extrema(GTK_DATABOX(box), &tmp_min, &tmp_max) < 0) return; /* swap y: min/max (high values -> higher on screen) */ if (!reset) { min.x = MIN(min.x, tmp_min.x); max.x = MAX(max.x, tmp_max.x); min.y = MAX(min.y, tmp_max.y); max.y = MIN(max.y, tmp_min.y); } else { min.x = tmp_min.x; max.x = tmp_max.x; min.y = tmp_max.y; max.y = tmp_min.y; } char tmp[64]; snprintf(tmp, sizeof(tmp), "y-Min: %0.0lf", max.y); gtk_label_set_text(GTK_LABEL(yscale_min), tmp); snprintf(tmp, sizeof(tmp), "y-Max: %0.0lf", min.y); gtk_label_set_text(GTK_LABEL(yscale_max), tmp); gtk_databox_set_canvas(GTK_DATABOX(box), min, max); } static void rescale_button_cb(GtkWidget *widget, gpointer data) { rescale_graphs(TRUE); } static void update_button_labels(void) { if (graph_mode & MOD_SINGLESHOT) gtk_button_set_label(GTK_BUTTON(mode_button), "Single Shot"); else if (graph_mode & MOD_CONTINUOUS) gtk_button_set_label(GTK_BUTTON(mode_button), "Continuous"); if (graph_mode & MOD_RUNNING) gtk_button_set_label(GTK_BUTTON(start_button), "Halt"); else if (graph_mode & MOD_STOPPED) gtk_button_set_label(GTK_BUTTON(start_button), "Start"); } static void mode_button_cb(GtkWidget *widget, gpointer data) { if (graph_mode & MOD_SINGLESHOT) graph_mode = (graph_mode & ~MOD_SINGLESHOT) | MOD_CONTINUOUS; else if (graph_mode & MOD_CONTINUOUS) graph_mode = (graph_mode & ~MOD_CONTINUOUS) | MOD_SINGLESHOT; update_button_labels(); } static void start_button_cb(GtkWidget *widget, gpointer data) { if (graph_mode & MOD_STOPPED) { graph_mode = (graph_mode & ~MOD_STOPPED) | MOD_RUNNING; gettimeofday(&base, NULL); } else if (graph_mode & MOD_RUNNING) { graph_mode = (graph_mode & ~MOD_RUNNING) | MOD_STOPPED; } update_button_labels(); } gint gui_graphtab_init(GtkNotebook *notebook) { GtkWidget *table = gtk_table_new(5, 6, FALSE); box = gtk_databox_new(); gtk_table_attach(GTK_TABLE(table), box, 1, 4, 1, 2, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); GtkWidget *scrollbar = gtk_hscrollbar_new(gtk_databox_get_hadjustment(GTK_DATABOX(box))); gtk_table_attach(GTK_TABLE(table), scrollbar, 1, 4, 2, 3, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL, 0, 0); scrollbar = gtk_vscrollbar_new(gtk_databox_get_vadjustment(GTK_DATABOX(box))); gtk_table_attach(GTK_TABLE(table), scrollbar, 4, 5, 1, 2, GTK_FILL, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); GtkWidget *ruler = gtk_hruler_new(); gtk_table_attach(GTK_TABLE(table), ruler, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND | GTK_SHRINK, GTK_FILL, 0, 0); gtk_databox_set_hruler(GTK_DATABOX(box), GTK_RULER(ruler)); ruler = gtk_vruler_new(); gtk_table_attach (GTK_TABLE (table), ruler, 0, 1, 1, 2, GTK_FILL, GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0); gtk_databox_set_vruler(GTK_DATABOX(box), GTK_RULER(ruler)); GdkColor color = { .red = 0xC000, .green = 0xC000, .blue = 0xC000, }; GtkDataboxGraph *grid = gtk_databox_grid_new(10, 10, &color, 1); gtk_databox_graph_add(GTK_DATABOX(box), grid); legend = gtk_label_new(NULL); gtk_table_attach(GTK_TABLE(table), legend, 1, 2, 3, 6, 0, 0, 5, 5); yscale_max = gtk_label_new("y-Max:"); gtk_table_attach(GTK_TABLE(table), yscale_max, 2, 3, 3, 4, 0, 0, 5, 5); yscale_min = gtk_label_new("y-Min:"); gtk_table_attach(GTK_TABLE(table), yscale_min, 2, 3, 4, 5, 0, 0, 5, 5); GtkWidget *rescale_button = gtk_button_new_with_label("Autoscale"); gtk_table_attach(GTK_TABLE(table), rescale_button, 2, 3, 5, 6, 0, 0, 10, 10); g_signal_connect(G_OBJECT(rescale_button), "clicked", G_CALLBACK(rescale_button_cb), NULL); start_button = gtk_button_new_with_label("Start"); gtk_table_attach(GTK_TABLE(table), start_button, 3, 4, 4, 5, 0, 0, 10, 10); g_signal_connect(G_OBJECT(start_button), "clicked", G_CALLBACK(start_button_cb), NULL); mode_button = gtk_button_new_with_label("Continuous"); gtk_table_attach(GTK_TABLE(table), mode_button, 3, 4, 5, 6, 0, 0, 10, 10); g_signal_connect(G_OBJECT(mode_button), "clicked", G_CALLBACK(mode_button_cb), NULL); update_button_labels(); GtkWidget *label = gtk_label_new(" Graph "); return gtk_notebook_append_page(GTK_NOTEBOOK(notebook), table, label); } int gui_graphtab_add_var(struct tdc_var *var) { GdkColor *color = get_color(); if (color == NULL) return -1; struct xygraph *graph = g_malloc0(sizeof(struct xygraph)); graph->graph = gtk_databox_lines_new(HISTORY, graph->xarr, graph->yarr, color, 1); graph->var = var; graph->color = color; int i; for (i = 0; i < HISTORY; i++) { graph->xarr[i] = i; graph->yarr[i] = 0; } var->privdata_graphtab = graph; graphlist = g_list_append(graphlist, graph); gtk_databox_graph_add(GTK_DATABOX(box), graph->graph); update_legend(); return 0; } void gui_graphtab_remove_var(struct tdc_var *var) { struct xygraph *graph = (struct xygraph *)var->privdata_graphtab; var->privdata_graphtab = NULL; gtk_databox_graph_remove(GTK_DATABOX(box), graph->graph); free_color(graph->color); graphlist = g_list_remove(graphlist, graph); update_legend(); g_free(graph); } static int calc_index(struct timeval *base, struct timeval *now, int step) { struct timeval diff; diff.tv_sec = now->tv_sec - base->tv_sec; diff.tv_usec = now->tv_usec - base->tv_usec; diff.tv_usec += diff.tv_sec * 1000000; diff.tv_usec /= 1000; return diff.tv_usec / step; } void gui_graphtab_update_var(struct tdc_var *var) { static struct timeval update; struct xygraph *graph = (struct xygraph *)var->privdata_graphtab; if (graph_mode & MOD_STOPPED) return; struct timeval now; gettimeofday(&now, NULL); int i = calc_index(&base, &now, 10); if (i < 0 || i >= HISTORY) { base.tv_sec = now.tv_sec; base.tv_usec = now.tv_usec; i = 0; if (graph_mode & MOD_SINGLESHOT) { graph_mode = (graph_mode & ~MOD_RUNNING) | MOD_STOPPED; update_button_labels(); } } float last_value = graph->yarr[graph->last_index]; while (graph->last_index != i) { graph->last_index++; if (graph->last_index == HISTORY) graph->last_index = 0; graph->yarr[graph->last_index] = last_value; } graph->yarr[i] = tdcvar_get_double(var); if (calc_index(&update, &now, 100) != 0) { update.tv_sec = now.tv_sec; update.tv_usec = now.tv_usec; rescale_graphs(FALSE); } }