351 lines
9.9 KiB
C
351 lines
9.9 KiB
C
/***************************************************************************
|
|
* 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 <sys/time.h>
|
|
#include <time.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gtkdatabox.h>
|
|
#include <gtkdatabox_grid.h>
|
|
#include <gtkdatabox_lines.h>
|
|
|
|
#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,"<span foreground=\"#%02x%02x%02x\">%s</span>\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);
|
|
}
|
|
}
|