/* GKrellM
|  Copyright (C) 1999-2021 Bill Wilson
|
|  Author:  Bill Wilson
|  Latest versions might be found at:  https://gkrellm.srcbox.net
|
|
|  GKrellM 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, either version 3 of the License, or
|  (at your option) any later version.
|
|  GKrellM 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, see http://www.gnu.org/licenses/
|
|
|  Additional permission under GNU GPL version 3 section 7
|
|  If you modify this program, or any covered work, by linking or
|  combining it with the OpenSSL project's OpenSSL library (or a
|  modified version of that library), containing parts covered by
|  the terms of the OpenSSL or SSLeay licenses, you are granted
|  additional permission to convey the resulting work.
|  Corresponding Source for a non-source form of such a combination
|  shall include the source code for the parts of OpenSSL used as well
|  as that of the covered work.
*/

#include "gkrellm.h"
#include "gkrellm-private.h"

#include <math.h>


static void	set_grid_resolution_spin_button(GkrellmChart *, gint);


  /* For grid images of height 2 pixels, make room at bottom of chartdata
  |  window so both pixel lines will show.
  */
#define GRID_HEIGHT_Y_OFFSET_ADJUST(cp) \
		(( gdk_pixbuf_get_height((cp)->bg_grid_piximage->pixbuf) < 2) ? 0 : 1)

  /* Map internal y values with origin at lower left to X screen coordinates
  |  which have origin at upper left.
  */
#define Y_SCREEN_MAP(cp,y)	((cp)->h - (y) - 1)


#define	MIN_CHARTHEIGHT	5
#define	MAX_CHARTHEIGHT	200

static GList	*chart_list;

/* ----------------------------------------------------------------------- */

static gint
computed_index(GkrellmChart *cp, gint i)
	{
	gint	x;

	x = (cp->position + i + 1) % cp->w;
	return x;
	}

GList *
gkrellm_get_chart_list(void)
	{
	return chart_list;
	}

gint
gkrellm_get_chart_scalemax(GkrellmChart *cp)
	{
	if (!cp)
		return 0;
	return cp->scale_max;
	}

gint
gkrellm_get_current_chartdata(GkrellmChartdata *cd)
	{
	if (!cd)
		return 0;
	return cd->data[cd->chart->position];
	}

gint
gkrellm_get_chartdata_data(GkrellmChartdata *cd, gint index)
	{
	gint	x;

	if (!cd)
		return 0;
	x = computed_index(cd->chart, index);
	return cd->data[x];
	}

void
gkrellm_clear_chart(GkrellmChart *cp)
	{
	if (!cp)
		return;
	gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_src_pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	gdk_draw_drawable(cp->bg_pixmap, _GK.draw1_GC, cp->bg_src_pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	if (gtk_widget_get_window(cp->drawing_area))
		gdk_draw_drawable(gtk_widget_get_window(cp->drawing_area), _GK.draw1_GC, cp->pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	}

void
gkrellm_clear_chart_pixmap(GkrellmChart *cp)
	{
	if (!cp)
		return;
	gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_src_pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	gdk_draw_drawable(cp->bg_pixmap, _GK.draw1_GC, cp->bg_src_pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	}

void
gkrellm_clean_bg_src_pixmap(GkrellmChart *cp)
	{
	if (!cp)
		return;
	if (!gkrellm_winop_draw_rootpixmap_onto_transparent_chart(cp))
		gdk_draw_drawable(cp->bg_src_pixmap, _GK.draw1_GC,
				cp->bg_clean_pixmap, 0, 0, 0, 0, cp->w, cp->h);
	cp->bg_sequence_id += 1;
	}

void
gkrellm_draw_chart_grid_line(GkrellmChart *cp, GdkPixmap *pixmap, gint y)
	{
	gint	h;

	if (!cp)
		return;
	gdk_drawable_get_size(cp->bg_grid_pixmap, NULL, &h);
	gdk_draw_drawable(pixmap, _GK.draw1_GC,
				cp->bg_grid_pixmap, 0, 0, cp->x, y, cp->w, h);
	}

void
gkrellm_draw_chart_to_screen(GkrellmChart *cp)
	{
	/* Draw the expose pixmap onto the screen.
	*/
	if (cp && gtk_widget_get_window(cp->drawing_area))
		gdk_draw_drawable(gtk_widget_get_window(cp->drawing_area), _GK.draw1_GC, cp->pixmap,
						0, 0,	0, 0,   cp->w, cp->h);
	}

static void
default_draw_chart_function(GkrellmChart *cp)
	{
	if (!cp)
		return;
	gkrellm_draw_chartdata(cp);
	gkrellm_draw_chart_to_screen(cp);
	}

void
gkrellm_set_draw_chart_function(GkrellmChart *cp, void (*func)(), gpointer data)
	{
	if (!cp)
		return;
	cp->draw_chart = func;
	cp->draw_chart_data = data;
	}

void
gkrellm_scale_chartdata(GkrellmChartdata *cd, gint multiplier, gint divisor)
	{
	gint	i;

	if (!cd || !cd->data || divisor < 1)
		return;
	for (i = 0; i < cd->chart->w; ++i)
		cd->data[i] = cd->data[i] * multiplier / divisor;
	cd->previous = cd->previous * multiplier / divisor;
	}

void
gkrellm_offset_chartdata(GkrellmChartdata *cd, gint offset)
	{
	gint	i;

	if (!cd || !cd->data)
		return;
	for (i = 0; i < cd->chart->w; ++i)
		cd->data[i] = cd->data[i] + offset;
	cd->previous = cd->previous + offset;
	}

void
gkrellm_reset_chart(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			i;

	cp->scale_max = 0;
	cp->maxval = 0;
	cp->redraw_all = TRUE;
	cp->position = cp->w - 1;
	cp->primed = FALSE;

	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		cd->maxval = 0;
		cd->previous = 0;
		if (cd->data)
			for (i = 0; i < cp->w; ++i)
				cd->data[i] = 0;
		}
	}

void
gkrellm_reset_and_draw_chart(GkrellmChart *cp)
	{
	if (!cp)
		return;
	gkrellm_reset_chart(cp);
	if (cp->draw_chart)
		(*(cp->draw_chart))(cp->draw_chart_data);
	}

void
gkrellm_monotonic_chartdata(GkrellmChartdata *cd, gboolean value)
	{
	if (cd)
		cd->monotonic = value;
	}

void
gkrellm_set_chartdata_draw_style(GkrellmChartdata *cd, gint dstyle)
	{
	if (cd)
		cd->draw_style = dstyle;
	}

void
gkrellm_set_chartdata_draw_style_default(GkrellmChartdata *cd, gint dstyle)
	{
	if (cd && cd->chart->config && !cd->chart->config->config_loaded)
		cd->draw_style = dstyle;
	}

void
gkrellm_set_chartdata_flags(GkrellmChartdata *cd, gint flags)
	{
	if (cd)
		cd->flags = flags;
	}

static void
gk_draw_vertical_line(GdkDrawable *drawable, GdkGC *gc, gint x, gint y1, gint y2)
	{
	if (y1 == y2)
		gdk_draw_point(drawable, gc, x, y1);
	else
		gdk_draw_line(drawable, gc, x, y1, x, y2);
	}


static gint
chartdata_ycoord(GkrellmChart *cp, GkrellmChartdata *cd, gint yd)
	{
	glong	y;
	guint64	Y;

	if (cp->scale_max <= 0)
		cp->scale_max = 1;

	if (yd > 2000000000 / MAX_CHARTHEIGHT)
		{
		Y = (guint64) yd * (guint64) cd->h;
		y = Y / cp->scale_max;
		}
	else
		y = ((glong) yd * cd->h / cp->scale_max);

	if (y < 0)
		y = 0;
	if (y >= cd->h)
		y = cd->h - 1;
	if (cd->inverted)
		y = cd->h - y - 1;
	y += cd->y;
	return Y_SCREEN_MAP(cp, y);
	}

static void
draw_layer_grid_lines(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			y, y0, h, grid_res, lines;
	gint			active_split, current_split;
	gboolean		do_next_split, done_once_per_split, tmp;

	gdk_draw_drawable(cp->bg_pixmap, _GK.draw1_GC,
				cp->bg_src_pixmap, 0, 0,  0, 0,  cp->w, cp->h);
	do_next_split = TRUE;
	for (active_split = 0; do_next_split; ++active_split)
		{
		do_next_split = FALSE;
		done_once_per_split = FALSE;
		current_split = 0;
		for (list = cp->cd_list; list; list = list->next)
			{
			cd = (GkrellmChartdata *) list->data;
			if (cd->hide)
				continue;
			current_split += cd->split_chart;
			if (active_split != current_split)
				{
				if (current_split > active_split)
					do_next_split = TRUE;
				continue;
				}
			gdk_draw_drawable(cd->layer.pixmap, _GK.draw1_GC,
					*(cd->layer.src_pixmap), 0, 0,  0, 0,  cp->w, cp->h);

			grid_res = cp->config->grid_resolution;
			lines = cp->scale_max / grid_res;
			if (lines && cd->h / lines > 2)	/* No grids if h is too small */
				{
				for (y = 0; y <= cp->scale_max; y += grid_res)
					{
					if (   _GK.bg_grid_mode == GRID_MODE_RESTRAINED
						&& ((y == 0 && cp->y == 0) || y == cp->scale_max)
					   )
						continue;
					tmp = cd->inverted;	  /* Draw grid lines in one direction*/
					cd->inverted = FALSE; /* else, may not line up by 1 pixel*/
					y0 = chartdata_ycoord(cp, cd, y);
					cd->inverted = tmp;
					gdk_drawable_get_size(cd->layer.grid_pixmap, NULL, &h);
					gdk_draw_drawable(cd->layer.pixmap, _GK.draw1_GC,
						cd->layer.grid_pixmap, 0, 0, cp->x, y0, cp->w, h);

					if (!done_once_per_split)
						{
						gdk_drawable_get_size(cp->bg_grid_pixmap, NULL, &h);
						gdk_draw_drawable(cp->bg_pixmap, _GK.draw1_GC,
							cp->bg_grid_pixmap, 0, 0, cp->x, y0, cp->w, h);
						}
					}
				}
			if (current_split > 0 && !done_once_per_split)
				{
				y = cd->y - 1;		/* Get separator y value */
				y -= GRID_HEIGHT_Y_OFFSET_ADJUST(cp);
				if (y >= 0)
					{
					gdk_draw_drawable(cp->bg_pixmap, _GK.draw1_GC,
							_GK.bg_separator_pixmap,
							0, 0, cp->x, Y_SCREEN_MAP(cp, y),
							cp->w, _GK.bg_separator_height);
					}
				}
			done_once_per_split = TRUE;
			}
		}
	}

  /* Return TRUE as long as there is a next split with impulse data needing
  |  to be drawn.
  */
static gboolean
draw_chartdata_impulses(GkrellmChart *cp, GList *cd_list,
			gint i0, gint active_split)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			n, x, y, y0, y1, yN, yI;
	gint			current_split;
	gboolean		need_next_split = FALSE;

	if (!cd_list)
		return FALSE;
	for (n = i0; n < cp->w; ++n)
		{
		x = computed_index(cp, n);
		y0 = y1 = -1;
		yN = yI = 0;
		current_split = 0;
		for (list = cp->cd_list; list; list= list->next)
			{
			cd = (GkrellmChartdata *) list->data;
			if (cd->hide)
				continue;
			current_split += cd->split_chart;
			if (   cd->draw_style != CHARTDATA_IMPULSE
				|| current_split != active_split
			   )
				{
				if (   current_split > active_split
					&& cd->draw_style == CHARTDATA_IMPULSE
				   )
					need_next_split = TRUE;
				continue;
				}
			if (cd->inverted)
				{
				if (y1 < 0)
					y1 = chartdata_ycoord(cp, cd, 0);
				yI += cd->data[x];
				y = chartdata_ycoord(cp, cd, yI);
				if (cd->data[x] > 0)
					gk_draw_vertical_line(cd->data_bitmap, _GK.bit1_GC, n, y1, y);
				y1 = y;
				}
			else
				{
				if (y0 < 0)
					y0 = chartdata_ycoord(cp, cd, 0);
				yN += cd->data[x];
				y = chartdata_ycoord(cp, cd, yN);
				if (cd->data[x] > 0)
					gk_draw_vertical_line(cd->data_bitmap, _GK.bit1_GC, n, y0, y);
				y0 = y;
				}
			}
		}
	/* Push the grided pixmaps through the data bitmaps onto the expose pixmap
	*/
	current_split = 0;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		current_split += cd->split_chart;
		if (cd->draw_style == CHARTDATA_LINE || current_split != active_split)
			continue;
		if (cd->maxval > 0)
			{
			y0 = chartdata_ycoord(cp, cd, 0);
			y1 = chartdata_ycoord(cp, cd, cd->maxval);
			gdk_gc_set_clip_mask(_GK.draw1_GC, cd->data_bitmap);
			if (cd->inverted)
				gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cd->layer.pixmap,
						0, y0,  0, y0,  cp->w, y1 - y0 + 1);
			else
				gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cd->layer.pixmap,
						0, y1,  0, y1,  cp->w, y0 - y1 + 1);
			}
		}
	return need_next_split;
	}

static gint
fix_y(gint yn, gint yp, gint ypp)
	{
	gint	y;

	if (yp > ypp && yn < yp)
		{
		y = (yp + yn) / 2;
		if (y < ypp)
			y = ypp;
		}
	else if (yp < ypp && yn > yp)
		{
		y = (yp + yn) / 2;
		if (y > ypp)
			y = ypp;
		}
	else
		y = yp;
	return y;
	}

static void
draw_chartdata_lines(GkrellmChart *cp, GkrellmChartdata *cd, gint i0)
	{
	gint	n, x, xp, y, y0, y1;

	y0 = chartdata_ycoord(cp, cd, 0);
	for (n = i0; n < cp->w; ++n)
		{
		x = computed_index(cp, n);
		y1 = chartdata_ycoord(cp, cd, cd->data[x]);
		if (n == 0)
			cd->y_p = y1;
		else if (!cd->y_p_valid && i0 == cp->w - 1)
			{
			if ((xp = x - 1) < 0)
				xp = cp->w - 1;
			cd->y_p = cd->y_pp = chartdata_ycoord(cp, cd, cd->data[xp]);
			}
		y = fix_y(y1, cd->y_p, cd->y_pp);
		cd->y_pp = y;
		cd->y_p = y1;
		cd->y_p_valid = FALSE;	/* Need a store_chartdata to make it valid */
		if (cd->data[x] > 0 || (cd->inverted ? (y > y0) : (y < y0)))
			{
			gk_draw_vertical_line(cd->data_bitmap, _GK.bit1_GC, cp->x + n, y, y1);
			}
		}
	/* Push the grided pixmap through the data bitmap onto the expose pixmap
	*/
	if (cd->maxval > 0)
		{
		y0 = chartdata_ycoord(cp, cd, 0);
		y1 = chartdata_ycoord(cp, cd, cd->maxval);
		gdk_gc_set_clip_mask(_GK.draw1_GC, cd->data_bitmap);
		if (cd->inverted)
			gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cd->layer.pixmap,
					0, y0,  0, y0,  cp->w, y1 - y0 + 1);
		else
			gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cd->layer.pixmap,
					0, y1,  0, y1,  cp->w, y0 - y1 + 1);
		}
	}

  /* See the README for description of auto grid resolution behavior.
  */
static void
set_auto_grid_resolution(GkrellmChart *cp, gint maxval)
	{
	GkrellmChartconfig	*cf = cp->config;
	gint				grids, grid_res, maxval_base;

	if (maxval <= cp->maxval_auto_base)
		maxval = cp->maxval_auto_base;
	else
		{
		if (maxval > cp->maxval_peak)
			cp->maxval_peak = maxval;
		maxval_base = maxval / FULL_SCALE_GRIDS;
		if (maxval_base > cp->maxval_auto_base)
			cp->maxval_auto_base = maxval_base;
		}
	cp->maxval_auto = maxval;

	grids = cf->fixed_grids;
	if (grids == 0)		/* Auto grids mode */
		grid_res = gkrellm_125_sequence(cp->maxval_auto_base, cf->sequence_125,
					cf->low, cf->high, TRUE, FALSE);
	else
		{
		if (cf->auto_resolution_stick)
			maxval = cp->maxval_peak;
		grid_res = gkrellm_125_sequence(maxval / grids, cf->sequence_125,
					cf->low, cf->high, TRUE, TRUE);
		}
	if (grid_res != cf->grid_resolution)
		{
		cf->grid_resolution = grid_res;
		set_grid_resolution_spin_button(cp, grid_res);
		if (cf->cb_grid_resolution && !cf->cb_block)
			(*cf->cb_grid_resolution)(cf, cf->cb_grid_resolution_data);
		cp->redraw_all = TRUE;
		}
	cp->auto_recalibrate_delay = 0;
	}

static gboolean
auto_recalibrate(GkrellmChart *cp)
	{
	if (++cp->auto_recalibrate_delay < 10)
		return FALSE;
	cp->maxval_peak = 0;
	cp->maxval_auto_base = 0;
	return TRUE;
	}

static gint
setup_chart_scalemax(GkrellmChart *cp)
	{
	GkrellmChartconfig *cf = cp->config;
	glong			scalemax;
	gint			grid_res, i0;

	/* maxval may change at any gkrellm_store_chartdata(), so at each chart
	|  draw compute a scalemax and compare to last cp->scale_max.
	|  Redraw grided background if different.
	*/
	if (cf->auto_grid_resolution)
		{
		if (   cp->maxval != cp->maxval_auto
			&& (   cp->maxval > cp->maxval_auto
				|| cp->maxval_auto != cp->maxval_auto_base
			   )
		   )
			set_auto_grid_resolution(cp, cp->maxval);
		else if (   !cf->auto_resolution_stick
				 && cp->maxval < cp->maxval_auto_base / FULL_SCALE_GRIDS
				)
			{
			if (auto_recalibrate(cp))
				set_auto_grid_resolution(cp, cp->maxval);
			}
		else
			cp->auto_recalibrate_delay = 0;
		}
	grid_res = cf->grid_resolution;
	if (cf->fixed_grids)
		scalemax = grid_res * cf->fixed_grids;
	else	/* Auto scale to cp->maxval */
		{
		if (cp->maxval == 0)
			scalemax = grid_res;
		else
			scalemax = ((cp->maxval - 1) / grid_res + 1) * grid_res;
		if (cp->previous_total && scalemax > grid_res * FULL_SCALE_GRIDS)
			scalemax = grid_res * FULL_SCALE_GRIDS;
		}
	if (scalemax != cp->scale_max || cp->redraw_all)
		{
		cp->redraw_all = FALSE;
		i0 = 0;				/* Will draw all data on chart */
		cp->scale_max = scalemax;
		draw_layer_grid_lines(cp);
		}
	else
		i0 = cp->w - 1;		/* Will draw the last data point only */
	return i0;
	}

void
gkrellm_draw_chartdata(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			i0, active_split, current_split;
	gboolean		have_impulse_splits = FALSE;

	if (!cp)
		return;
	i0 = setup_chart_scalemax(cp);
	gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_pixmap,
						0, 0,	0, 0,   cp->w, cp->h);

	current_split = active_split = 0;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		current_split += cd->split_chart;
		if (!have_impulse_splits && cd->draw_style == CHARTDATA_IMPULSE)
			{
			have_impulse_splits = TRUE;
			active_split = current_split;
			}
		/* Clear the area of the data bitmaps that data will be drawn into
		*/
		gdk_draw_rectangle(cd->data_bitmap, _GK.bit0_GC, TRUE,
					i0, 0, cd->w - i0, cp->h);
		}

	for (  ; have_impulse_splits; ++active_split)
		have_impulse_splits = draw_chartdata_impulses(cp, cp->cd_list,
					i0, active_split);

	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->draw_style == CHARTDATA_LINE && !cd->hide)
			draw_chartdata_lines(cp, cd, i0);
		}
	gdk_gc_set_clip_mask(_GK.draw1_GC, NULL);
	}

void
gkrellm_alloc_chartdata(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;

	if (!cp)
		return;
	int w = cp->w;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->w == w && cd->data)
			continue;
		cd->w = w;
		if (cd->data)
			g_free(cd->data);
		cd->data = (gulong *) g_new0(gulong, w);
		cd->maxval = 0;
		cp->position = cp->w - 1;
		cp->tail = cp->position;
		}
	cp->alloc_width = w;
	cp->maxval = 0;
	cp->scale_max = 0;
	cp->redraw_all = TRUE;
	}

static void
scroll_chartdata_bitmaps(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;

	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		gdk_draw_drawable(cd->data_bitmap, _GK.bit1_GC, cd->data_bitmap,
						1, 0, 0, 0, cp->w - 1, cp->h);
		}
	}

static gboolean
scan_for_impulse_maxval(GkrellmChart *cp, gint active_split)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			N, I;
	gint			i, current_split;
	gboolean		need_next_split = FALSE;

	for (i = 0; i < cp->w; ++i)
		{
		/* N is normal and I inverted cumulative impulse data/split
		*/
		N = I = 0;
		current_split = 0;
		for (list = cp->cd_list; list; list = list->next)
			{
			cd = (GkrellmChartdata *) list->data;
			if (cd->hide)
				continue;
			current_split += cd->split_chart;
			if (   cd->draw_style != CHARTDATA_IMPULSE
				|| current_split != active_split
			   )
				{
				if (   current_split > active_split
					&& cd->draw_style == CHARTDATA_IMPULSE
				   )
					need_next_split = TRUE;
				continue;
				}
			if (cd->inverted)
				{
				I += cd->data[i];
				if (I > cd->maxval)
					cd->maxval = I;
				}
			else
				{
				N += cd->data[i];
				if (N > cd->maxval)
					cd->maxval = N;
				}
			if (N + I > cp->maxval)
				cp->maxval = N + I;
			}
		}
	return need_next_split;
	}

static void
scan_for_maxval(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gint			i, current_split, active_split;
	gboolean		have_impulse_splits = FALSE;

	cp->maxval = 0;
	current_split = 0;
	active_split = 0;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		cd->maxval = 0;
		current_split += cd->split_chart;
		if (cd->draw_style == CHARTDATA_LINE)
			for (i = 0; i < cp->w; ++i)
				{
				if (cd->data[i] > cd->maxval)
				    cd->maxval = cd->data[i];
				if (cd->maxval > cp->maxval)
					cp->maxval = cd->maxval;
				}
		if (!have_impulse_splits && cd->draw_style == CHARTDATA_IMPULSE)
			{
			have_impulse_splits = TRUE;
			active_split = current_split;
			}
		}
	for ( ; have_impulse_splits; ++active_split)
		have_impulse_splits = scan_for_impulse_maxval(cp, active_split);
	}

void
gkrellm_store_chartdata(GkrellmChart *cp, gulong total, ...)
	{
	va_list			args;

	if (!cp)
		return;
	va_start(args, total);
	gkrellm_store_chartdatav(cp, total, args);
	va_end(args);
	}

void
gkrellm_store_chartdatav(GkrellmChart *cp, gulong total, va_list args)
	{
	GList			*list;
	GkrellmChartdata *cd;
	gulong			range, total_diff;
	gint			n, N_discard, I_discard, N, I;
	gint			active_split, current_split;
	gboolean		need_scan = FALSE;

	if (!cp)
		return;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		//FIXME: missing check for number of passed varargs passed in "args"
		cd->current = va_arg(args, gulong);
		if (!cd->monotonic)
			{
			cd->previous = 0;
			cp->previous_total = 0;		/* All or none if total is used */
			}
		/* Prime the pump.  Also handle data wrap around or reset to zero.
		*/
		cd->wrap = 0;
		if (!cp->primed)
			cd->previous = cd->current;
		else if (cd->current < cd->previous)
			{
			cd->wrap = (gulong) -1 - cd->previous;
			cd->previous = 0;
			}
		}
	if (total < cp->previous_total || !cp->primed)
		cp->previous_total = total;	  /* Wrap around, this store won't scale */
	total_diff = total - cp->previous_total;
	cp->previous_total = total;

	/* Increment position in circular buffer and remember the data
	|  value to be thrown out.
	*/
	cp->position = (cp->position + 1) % cp->w;
	cp->tail = (cp->tail + 1) % cp->w;
	n = cp->position;
	active_split = current_split = 0;
	N_discard = I_discard = 0;

	/* N is normal and I inverted cumulative impulse data/split
	*/
	N = I = 0;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		cd->discard = cd->data[cp->tail];
		cd->data[n] = (gulong)(cd->current - cd->previous) + cd->wrap;
		cd->previous = cd->current;

		/* If using totals, scale the stored data to range between 0 and the
		|  max chart value.  Max chart value is number of grids * grid res.
		|  No. of grids is 5 in auto grid mode.  For plotting data as a %.
		*/
		if (total_diff > 0)
			{
			range = (cp->config->fixed_grids ? cp->config->fixed_grids
							: FULL_SCALE_GRIDS) * cp->config->grid_resolution;
			if (range != total_diff)
				cd->data[n] = cd->data[n] * range / total_diff;
			}
		if (cd->hide || need_scan)
			continue;

		/* Compare discarded data to new data (accounting for stacked impulse
		|  data) and decide if a new maxval must be set (new data > maxval)
		|  or if a complete rescan of the data is needed to find a new
		|  maxval (a discard > a new).
		*/
		current_split += cd->split_chart;
		if (cd->draw_style == CHARTDATA_IMPULSE)
			{
			if (current_split != active_split)
				{
				active_split = current_split;
				N_discard = I_discard = 0;
				N = I = 0;
				}
			if (cd->inverted)
				{
				I_discard += cd->discard;
				I += cd->data[n];
				if (I_discard && I_discard >= cd->maxval)
					need_scan = TRUE;
				else if (I > cd->maxval)
					cd->maxval = I;
				}
			else
				{
				N_discard += cd->discard;
				N += cd->data[n];
				if (N_discard && N_discard >= cd->maxval)
					need_scan = TRUE;
				else if (N > cd->maxval)
					cd->maxval = N;
				}
			if (N_discard + I_discard >= cd->maxval)
				need_scan = TRUE;
			else if (N + I > cp->maxval)
				cp->maxval = N + I;
			}
		else if (cd->draw_style == CHARTDATA_LINE)
			{
			cd->y_p_valid = TRUE;
			if (cd->discard && cd->discard >= cd->maxval)
				need_scan = TRUE;
			else
				{
				if (cd->data[n] > cd->maxval)
					cd->maxval = cd->data[n];
				if (cd->maxval > cp->maxval)
					cp->maxval = cd->maxval;
				}
			}
		}
	cp->primed = TRUE;
	if (need_scan || cp->redraw_all)
		scan_for_maxval(cp);
	scroll_chartdata_bitmaps(cp);
	}



/* =================================================================== */

static void
chart_destroy_text_run_list(GkrellmChart *cp)
	{
	GList			*list;

	if (!cp || !cp->text_run_list)
		return;
	for (list = cp->text_run_list; list; list = list->next)
		g_free(((GkrellmTextRun *) list->data)->text);
	gkrellm_free_glist_and_data(&cp->text_run_list);
	}

static gint
chartdata_text_y(GkrellmChart *cp, char key, gint height,
				gint y_ink, gint shadow)
	{
	GList				*list;
	GkrellmChartdata	*cd;
	gint				n, y;

	n = key - '0';
	y = -100;
	if (n >= 0)
		{
		list = g_list_nth(cp->cd_list, n / 2);
		if (list)
			{
			cd = (GkrellmChartdata *) list->data;
			if (!cd->hide)
				{
				if (n & 1)	/* Justify 2 pixels from top of ChartData view */
					{
					y = cd->y + cd->h + y_ink - 3;
					}
				else		/* Justify to bottom of ChartData view */
					{
					y = cd->y + height + y_ink + shadow - 1;
					}
				y = Y_SCREEN_MAP(cp, y);
				}
			}
		}
	return y;
	}

void
gkrellm_chart_reuse_text_format(GkrellmChart *cp)
	{
	cp->text_format_reuse = TRUE;
	}

void
gkrellm_draw_chart_text(GkrellmChart *cp, gint style_id, gchar *str)
	{
	GList			*list;
	GkrellmTextRun	*tr;
	GkrellmTextstyle *ts, *ts_default, *ts_alt;
	gchar			c, *s, *t;
	gint			h, field_width, fw;
	gint			offset;
	gint			xx, x, y, center, shadow;
	gboolean		right, set_fw, fw_right;

	if (!cp || !str)
		return;

	/* Assume text_format will be changed at each call unless caller has said
	|  we can reuse it or the whole string compares equal.
	*/
	if (   !cp->text_format_reuse
		&& !gkrellm_dup_string(&cp->text_format_string, str)
	   )
		cp->text_format_reuse = TRUE;

	if (_GK.debug_level & DEBUG_CHART_TEXT)
		{
		g_debug("\n");
		if (!cp->text_format_reuse)
			g_debug("draw_chart_text: [%s]\n", str);
		}

	if (   !cp->text_format_reuse
		|| cp->text_run_sequence_id != cp->bg_sequence_id
	   )
		chart_destroy_text_run_list(cp);
	cp->text_run_sequence_id = cp->bg_sequence_id;
	cp->text_format_reuse = FALSE;

	ts_default = gkrellm_chart_textstyle(style_id);
	ts_alt = gkrellm_chart_alt_textstyle(style_id);

	x = xx = 2;
	if (!cp->text_run_list)
		gkrellm_text_extents(ts_default->font, _("Ag8"), 3, NULL,
					&cp->h_text, &cp->baseline_ref, &cp->y_ink);

	y = 2 - cp->y_ink;
	h = fw = 0;
	tr = NULL;
	size_t text_length = 0;
	for (list = cp->text_run_list, s = str; *s; s += text_length)
		{
		if (!list)
			{
			tr = g_new0(GkrellmTextRun, 1);
			cp->text_run_list = g_list_append(cp->text_run_list, tr);
			}
		else
			{
			tr = (GkrellmTextRun *) list->data;
			list = list->next;
			tr->cache_valid = TRUE;
			}
		c = '\0';
		center = 0;
		right = FALSE;
		set_fw = FALSE;
		fw_right = FALSE;
		ts = ts_default;
		field_width = 0;
		shadow = ts_default->effect ? 1 : 0;
		while (*s == '\\')
			{
			if ((c = *(++s)) != '\0')
				++s;
			if (c == 'n')
				{
				y += cp->h_text + 1;
				x = xx;
				}
			else if (c == 'c')
				center = 1;
			else if (c == 'C')
				center = 2;
			else if (c == 'N')
				{
				x = xx;		/* A conditional newline.  If previous string */
				if (h > 2)	/* was spaces, no nl.  A space has nonzero a  */
					y += cp->h_text + 1;
				}
			else if (c == 'b')
				{
				y = cp->h - cp->h_text - cp->y_ink - 1;
				x = xx;
				}
			else if (c == 't')
				{
				y = 2 - cp->y_ink;
				x = xx;
				}
			else if (c == 'r')
				right = TRUE;
			else if (c == 'p')
				{
				y -= cp->h_text + 1;
				x = xx;
				}
			else if (c == 'w')
				set_fw = TRUE;
			else if (c == 'a')
				field_width = fw;
			else if (c == 'e')
				{
				field_width = fw;
				fw_right = TRUE;
				}
			else if (c == 'f')
				{
				ts = ts_alt;
				shadow = ts_alt->effect ? 1 : 0;
				}
			else if (c == 'x' && isdigit((unsigned char)*s))
				xx = *s++ - '0';
			else if (c == 'y' && isdigit((unsigned char)*s))
				y = *s++ - '0';
			else if (c == 'D')
				{
				y = chartdata_text_y(cp, *s++, cp->h_text, cp->y_ink, shadow);
				x = xx;
				}
			}
		t = strchr(s, (gint) '\\');
		if (t)
			text_length = t - s;
		else
			text_length = strlen(s);

		if (y == -100)	/* asked for a chartdata that is hidden */
			continue;

		if (   !tr->cache_valid || !tr->text
			|| strncmp(tr->text, s, text_length)
			|| strlen(tr->text) != text_length
		   )
			{
			gkrellm_text_extents(ts->font, s, text_length,
						&tr->w, &tr->h, &tr->baseline, &tr->y_ink);
			tr->cache_valid = FALSE;
			g_free(tr->text);
			tr->text = g_strndup(s, text_length);
			}
		h = tr->h;

		if (set_fw)
			{
			fw = tr->w + shadow;
			continue;
			}
		if (center == 1)
			x = (cp->w - tr->w) / 2;
		else if (center == 2)
			x = cp->w / 2;
		else if (fw_right)
			x = x + fw - tr->w - shadow;
		else if (right)
			x = cp->w - tr->w - 2 - shadow;
		if (text_length > 1 || (text_length == 1 && *s != ' '))
			{
			if (x != tr->x || y != tr->y)
				tr->cache_valid = FALSE;
			tr->x = x;
			tr->y = y;

			if (_GK.debug_level & DEBUG_CHART_TEXT)
				{
				gchar	buf[128];

				strncpy(buf, s, text_length);
				buf[text_length] = '\0';
				g_debug("draw_chart_text: [%s] ", buf);
				}

			offset = cp->baseline_ref - tr->baseline;	/* align baselines */
			if (_GK.chart_text_no_fill)
	    		gkrellm_draw_text(cp->pixmap, ts, x, y + offset, s,
						text_length);
			else /* Default text draw effect is fill solid and can use cache */
				{
				if (!tr->cache_valid)
					{
					if (_GK.debug_level & DEBUG_CHART_TEXT)
						g_debug("pango ");
					gdk_draw_drawable(cp->bg_text_pixmap, _GK.draw1_GC,
							cp->bg_pixmap,
							x - 1, y + tr->y_ink + offset - 1,
							x - 1, y + tr->y_ink + offset - 1,
							tr->w + shadow + 2, tr->h + shadow + 1);
		    		gkrellm_draw_text(cp->bg_text_pixmap, ts, x, y + offset, s,
							text_length);
					}
				if (_GK.debug_level & DEBUG_CHART_TEXT)
					g_debug("x=%d y=%d w=%d h=%d\n",
							x - 1, y + tr->y_ink + offset - 1,
							tr->w + shadow + 2, tr->h + shadow + 1);
				gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_text_pixmap,
						x - 1, y + tr->y_ink + offset - 1,
						x - 1, y + tr->y_ink + offset - 1,
						tr->w + shadow + 2, tr->h + shadow + 1);
				}
			}
		if (field_width && !fw_right)
			x += (field_width > tr->w) ? field_width : tr->w;
		else
			x += tr->w + shadow;
		}
	}

gint
gkrellm_draw_chart_label(GkrellmChart *cp, GkrellmTextstyle *ts,
				gint x, gint y, gchar *s)
	{
	gint	w, h, y_ink;

	if (!cp || !ts || !s)
		return x;

	gkrellm_text_extents(ts->font, s, strlen(s), &w, &h, NULL, &y_ink);

	gdk_draw_drawable(cp->pixmap, _GK.draw1_GC, cp->bg_pixmap,
				x, y, x, y, w + ts->effect, h + ts->effect);
    gkrellm_draw_string(cp->pixmap, ts, x, y - y_ink, s);

	return x + w + ts->effect;
	}

static void
gkrellm_destroy_chartdata_list(GList **cd_list_head)
	{
	GList			*list;
	GkrellmChartdata *cd;

	if (!cd_list_head)
		return;
	for (list = *cd_list_head; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->label)
			g_free(cd->label);
		if (cd->data)
			g_free(cd->data);
		if (cd->data_bitmap)
			g_object_unref(G_OBJECT(cd->data_bitmap));
		if (cd->layer.pixmap)
			g_object_unref(G_OBJECT(cd->layer.pixmap));
		/* cd->layer.src_pixmap & cd->layer.grid_pixmap must be handled by
		|  creating monitor.
		*/
		}
	gkrellm_free_glist_and_data(cd_list_head);
	}


static void
free_chart_pixmaps(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;

	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		gkrellm_free_bitmap(&cd->data_bitmap);
		gkrellm_free_pixmap(&cd->layer.pixmap);
		/* cd->layer.src_pixmap & cd->layer.grid_pixmap must be handled by
		|  creating monitor.
		*/
		}
	/* If new theme or size, the cd_list will not be destroyed so I can
	|  reuse the data arrays.   Pixmaps will be recreated.
	*/
	cp->cd_list_index = 0;

	gkrellm_free_pixmap(&cp->bg_pixmap);
	gkrellm_free_pixmap(&cp->bg_text_pixmap);
	gkrellm_free_pixmap(&cp->bg_src_pixmap);
	gkrellm_free_pixmap(&cp->bg_grid_pixmap);

	gkrellm_free_pixmap(&cp->bg_clean_pixmap);
	gkrellm_free_bitmap(&cp->bg_mask);

	gkrellm_free_pixmap(&cp->pixmap);

	gkrellm_free_pixmap(&cp->top_spacer.clean_pixmap);
	gkrellm_free_pixmap(&cp->top_spacer.pixmap);
	gkrellm_free_bitmap(&cp->top_spacer.mask);
	gkrellm_free_pixmap(&cp->bottom_spacer.clean_pixmap);
	gkrellm_free_pixmap(&cp->bottom_spacer.pixmap);
	gkrellm_free_bitmap(&cp->bottom_spacer.mask);
	}

static void
destroy_chart_data(GkrellmChart *cp)
	{
	GList			*list;
	GkrellmChartdata *cd;

	free_chart_pixmaps(cp);
	chart_destroy_text_run_list(cp);
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->label)
			g_free(cd->label);
		if (cd->data)
			g_free(cd->data);
		cd->label = NULL;
		cd->data = NULL;
		}
	/* Don't free the cd_list.  It is in the GkrellmChartconfig struct.
	*/
	}

  /* Destroy everything inside a chart except leave cp->config alone since
  |  the config is managed by each monitor.
  */
void
gkrellm_chart_destroy(GkrellmChart *cp)
	{
	gint	h_spacers = 0;

	if (!cp)
		return;

	if (cp->top_spacer.image)
		h_spacers = cp->top_spacer.height;
	if (cp->bottom_spacer.image)
		h_spacers += cp->bottom_spacer.height;

	gkrellm_freeze_side_frame_packing();
	if (cp->panel)
		gkrellm_panel_destroy(cp->panel);
	destroy_chart_data(cp);
	gtk_widget_destroy(cp->box);
	chart_list = g_list_remove(chart_list, cp);
	gkrellm_chartconfig_window_destroy(cp);
	if (cp->shown)
		gkrellm_monitor_height_adjust(-(cp->h + h_spacers));
	g_free(cp);
	gkrellm_thaw_side_frame_packing();
	}

void
gkrellm_chartconfig_destroy(GkrellmChartconfig **cf)
	{
	if (!cf || !*cf)
		return;
	gkrellm_destroy_chartdata_list((*cf)->chart_cd_list);
	g_free(*cf);
	*cf = NULL;
	}

void
gkrellm_chart_bg_piximage_override(GkrellmChart *cp,
			GkrellmPiximage *bg_piximage, GkrellmPiximage *bg_grid_piximage)
	{
	if (!cp || !bg_piximage || !bg_grid_piximage)
		return;
	cp->bg_piximage = bg_piximage;
	cp->bg_grid_piximage = bg_grid_piximage;
	cp->bg_piximage_override = TRUE;
	}


static void
set_chartdata_split_heights(GkrellmChart *cp)
	{
	GList			*list, *list1;
	GkrellmChartdata *cd, *cd1;
	gint			splits;
	gint			i, y0, h_avail, h_free, y_offset;

	for (i = 0, splits = 0, list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		if (++i > 1 && cd->split_chart)	/* Can't split before the first one */
			++splits;
		cd->split_share = 1.0;
		for (list1 = list->next; list1; list1 = list1->next)
			{
			cd1 = (GkrellmChartdata *) list1->data;
			if (!cd1->split_chart || cd1->hide)
				continue;
			cd->split_share = cd1->split_fraction;
			break;
			}
		}
	y_offset = GRID_HEIGHT_Y_OFFSET_ADJUST(cp);
	y0 = cp->y + y_offset;
	h_avail = cp->h - cp->y - ((splits + 1) * y_offset)
				- splits * _GK.bg_separator_height;
	h_free = h_avail;
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if (cd->hide)
			continue;
		cd->y = y0;
		for (list1 = list->next; list1; list1 = list1->next)
			if (!((GkrellmChartdata *) list1->data)->hide)
				break;
		if (!list1)
			cd->h = h_free;
		else
			cd->h = (gint) (cd->split_share * (gfloat) h_free);
		if (cd->h < 1)
			cd->h = 1;
		if (list1 && ((GkrellmChartdata *) list1->data)->split_chart)
			{
			y0 += cd->h + _GK.bg_separator_height + y_offset;
			h_free -= cd->h;
			}
		}
	}

GkrellmChartdata *
gkrellm_add_default_chartdata(GkrellmChart *cp, gchar *label)
	{
	GdkPixmap	**src_pixmap, *grid_pixmap;

	if (!cp)
		return NULL;
	if (cp->cd_list_index & 1)
		{
		src_pixmap = &_GK.data_in_pixmap;
		grid_pixmap = _GK.data_in_grid_pixmap;
		}
	else
		{
		src_pixmap = &_GK.data_out_pixmap;
		grid_pixmap = _GK.data_out_grid_pixmap;
		}
	return gkrellm_add_chartdata(cp, src_pixmap, grid_pixmap, label);
	}

  /* Need a GdkPixmap ** because the src pixmap can change (re-rendered at
  |  size or theme changes).
  */
GkrellmChartdata *
gkrellm_add_chartdata(GkrellmChart *cp, GdkPixmap **src_pixmap,
			GdkPixmap *grid_pixmap, gchar *label)
	{
	GtkWidget		*top_win	= gkrellm_get_top_window();
	GList			*list;
	GkrellmChartdata *cd;

	if (!cp || !src_pixmap || !grid_pixmap || !label)
		return NULL;

	/* To handle theme and vert size changes without losing data, reuse the
	|  GkrellmChartdata structs in the cd_list.
	*/
	list = g_list_nth(cp->cd_list, cp->cd_list_index++);
	if (!list)
		{
		cd = g_new0(GkrellmChartdata, 1);
		cp->cd_list = g_list_append(cp->cd_list, cd);
		cp->config->cd_list = cp->cd_list;
		cd->split_fraction = 0.5;
		}
	else
		cd = (GkrellmChartdata *) list->data;
	cd->chart = cp;
	gkrellm_dup_string(&cd->label, label);
	cd->monotonic = TRUE;
	cd->data_bitmap = gdk_pixmap_new(gtk_widget_get_window(top_win), cp->w, cp->h, 1);
	cd->layer.pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), cp->w, cp->h, -1);
	cd->layer.src_pixmap = src_pixmap;
	cd->layer.grid_pixmap = grid_pixmap;

	set_chartdata_split_heights(cp);
	return cd;
	}

void
gkrellm_render_data_grid_pixmap(GkrellmPiximage *im, GdkPixmap **pixmap,
			GdkColor *color)
	{
	GtkWidget   *top_win = gkrellm_get_top_window();
	gint		w, w_pixmap = 0, h = 0;

	w = gkrellm_chart_width();
	if (*pixmap)
		gdk_drawable_get_size(*pixmap, &w_pixmap, NULL);
	if (!*pixmap || w != w_pixmap)
		{
		if (im)
			{
			if ((h = gdk_pixbuf_get_height(im->pixbuf)) > 2)
				h = 2;
			gkrellm_scale_piximage_to_pixmap(im, pixmap, NULL, w, h);
			}
		else
			{
			gkrellm_free_pixmap(pixmap);
			*pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), w, 1, -1);
			if (color)
				gdk_gc_set_foreground(_GK.draw1_GC, color);
			else
				gdk_gc_set_foreground(_GK.draw1_GC, &_GK.in_color_grid);
			gdk_draw_rectangle(*pixmap, _GK.draw1_GC, TRUE, 0, 0, w, 1);
			}
		}
	}

void
gkrellm_render_data_pixmap(GkrellmPiximage *im, GdkPixmap **pixmap,
			GdkColor *color, gint h)
	{
	GtkWidget   *top_win = gkrellm_get_top_window();
	gint		w, w_pixmap = 0, h_pixmap = 0;

	w = gkrellm_chart_width();
	if (*pixmap)
		gdk_drawable_get_size(*pixmap, &w_pixmap, &h_pixmap);

	if (!*pixmap || w != w_pixmap || h != h_pixmap)
		{
		if (!gkrellm_scale_piximage_to_pixmap(im, pixmap, NULL, w, h))
			{
			*pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), w, h, -1);
			if (color)
				gdk_gc_set_foreground(_GK.draw1_GC, color);
			else
				gdk_gc_set_foreground(_GK.draw1_GC, &_GK.in_color_grid);
			gdk_draw_rectangle(*pixmap, _GK.draw1_GC, TRUE, 0,0,w,h);
			}
		}
	}

static void
render_default_data_pixmaps(GkrellmChart *cp)
	{
	GList		*list;
	GdkPixmap	*pixmap;
	gint		w, h, w_pixmap = 0;

	gkrellm_render_data_grid_pixmap(_GK.data_in_grid_piximage,
				&_GK.data_in_grid_pixmap, &_GK.in_color_grid);
	gkrellm_render_data_grid_pixmap(_GK.data_out_grid_piximage,
				&_GK.data_out_grid_pixmap, &_GK.out_color_grid);

	w = gkrellm_chart_width();
	pixmap = _GK.bg_separator_pixmap;
	if (pixmap)
		gdk_drawable_get_size(pixmap, &w_pixmap, NULL);
	if (!pixmap || w_pixmap != w)
		{
		if ((h = _GK.bg_separator_height) < 1 || h > 5)
			h = 2;
		if (_GK.bg_separator_piximage)
			gkrellm_scale_piximage_to_pixmap(_GK.bg_separator_piximage,
						&_GK.bg_separator_pixmap, NULL, w, h);
		else
			{
			GkrellmPiximage	*im;

			im = gkrellm_bg_panel_piximage(DEFAULT_STYLE_ID);
			gkrellm_scale_pixbuf_to_pixmap(im->pixbuf, &_GK.bg_separator_pixmap,
					NULL, w, h);
			}
		}

	h = 2;
	for (list = chart_list; list; list = list->next)
		{
		cp = (GkrellmChart *) list->data;
		if (cp->h > h)
			h = cp->h;
		}
	gkrellm_render_data_pixmap(_GK.data_in_piximage,
				&_GK.data_in_pixmap, &_GK.in_color, h);
	gkrellm_render_data_pixmap(_GK.data_out_piximage,
				&_GK.data_out_pixmap, &_GK.out_color, h);
	}

void
gkrellm_chart_setup(void)
	{
	gkrellm_free_pixmap(&_GK.data_in_pixmap);
	gkrellm_free_pixmap(&_GK.data_in_grid_pixmap);
	gkrellm_free_pixmap(&_GK.data_out_pixmap);
	gkrellm_free_pixmap(&_GK.data_out_grid_pixmap);
	gkrellm_free_pixmap(&_GK.bg_separator_pixmap);
	}

#if 0
static gint
compare_chartlist(gconstpointer a, gconstpointer b)
    {
	GkrellmChart	*cp_a	= (GkrellmChart *) a;
	GkrellmChart	*cp_b	= (GkrellmChart *) b;
	gint	result;

	if (cp_a->style_id < cp_b->style_id)
		result = -1;
	else (if cp_a->style_id > cp_b->style_id)
		result = 1;
	else
		result = 0;
	return result;
    }
#endif

static void
insert_in_chartlist(GkrellmChart *cp)
	{
	GList	*list;
	GkrellmChart	*cp_x;
	gint	position = 0;

	for (list = chart_list; list; list = list->next, ++position)
		{
		cp_x = (GkrellmChart *) list->data;
		if (cp_x->style_id > cp->style_id)
			{
			chart_list = g_list_insert(chart_list, cp, position);
			return;
			}
		}
	chart_list = g_list_append(chart_list, cp);
	}

void
gkrellm_chart_hide(GkrellmChart *cp, gboolean hide_panel)
	{
	gint	h_spacers = 0;

	if (!cp || !cp->shown)
		return;

	if (cp->top_spacer.image)
		h_spacers = cp->top_spacer.height;
	if (cp->bottom_spacer.image)
		h_spacers += cp->bottom_spacer.height;

	gtk_widget_hide(cp->box);
	gkrellm_freeze_side_frame_packing();
	if (hide_panel)
		gkrellm_panel_hide(cp->panel);
	gkrellm_monitor_height_adjust(- (cp->h + h_spacers));
	cp->shown = FALSE;
	gkrellm_thaw_side_frame_packing();
	}

void
gkrellm_chart_show(GkrellmChart *cp, gboolean show_panel)
	{
	gint	h_spacers = 0;

	if (!cp || cp->shown)
		return;

	if (cp->top_spacer.image)
		h_spacers = cp->top_spacer.height;
	if (cp->bottom_spacer.image)
		h_spacers += cp->bottom_spacer.height;

	gtk_widget_show(cp->box);
	gkrellm_freeze_side_frame_packing();
	if (show_panel)
		gkrellm_panel_show(cp->panel);
	cp->shown = TRUE;
	gkrellm_monitor_height_adjust(cp->h + h_spacers);
	gkrellm_thaw_side_frame_packing();
	}

gboolean
gkrellm_is_chart_visible(GkrellmChart *cp)
	{
	if (!cp)
		return FALSE;
	return cp->shown;
	}

gboolean
gkrellm_chart_enable_visibility(GkrellmChart *cp, gboolean new_vis,
					gboolean *current_vis)
	{
	gboolean	changed = FALSE;

	if (new_vis  && ! *current_vis)
		{
		gkrellm_chart_show(cp, TRUE);
		*current_vis  = TRUE;
		changed = TRUE;
		}
	if (!new_vis  && *current_vis)
		{
		gkrellm_chart_hide(cp, TRUE);
		*current_vis  = FALSE;
		changed = TRUE;
		}
	return changed;
	}

void
gkrellm_set_chart_height_default(GkrellmChart *cp, gint h)
	{
	if (!cp || (cp->config && cp->config->config_loaded))
		return;

	if (h < MIN_CHARTHEIGHT)
		h = MIN_CHARTHEIGHT;
	if (h > MAX_CHARTHEIGHT)
		h = MAX_CHARTHEIGHT;
	cp->h = h;
	}

static void
render_chart_margin_spacers(GkrellmChart *cp)
	{
	GkrellmMargin	*m;
	GkrellmSpacer	*ts, *bs;

	ts = &cp->top_spacer;
	bs = &cp->bottom_spacer;
	if (ts->image)
		gtk_container_remove(GTK_CONTAINER(ts->vbox), ts->image);
	if (bs->image)
		gtk_container_remove(GTK_CONTAINER(bs->vbox), bs->image);
	ts->image = bs->image = NULL;
	m = gkrellm_get_style_margins(cp->style);
	ts->piximage = cp->bg_piximage;
	ts->height   = m->top;
	bs->piximage = cp->bg_piximage;
	bs->height   = m->bottom;

	if (!gkrellm_render_spacer(ts, 0, m->top,
				_GK.frame_left_chart_overlap, _GK.frame_right_chart_overlap))
		gtk_widget_hide(ts->vbox);

	if (!gkrellm_render_spacer(bs,
				gdk_pixbuf_get_height(cp->bg_piximage->pixbuf) - m->bottom,
				m->bottom,
				_GK.frame_left_chart_overlap, _GK.frame_right_chart_overlap))
		gtk_widget_hide(bs->vbox);
	}

static gboolean
cb_chart_size_allocate(GtkWidget *widget, GtkAllocation *size,
				GkrellmChart *cp)
    {
    gdk_window_get_position(gtk_widget_get_window(cp->drawing_area), NULL, &cp->y_mapped);
	if (_GK.frame_left_chart_overlap > 0 || _GK.frame_right_chart_overlap > 0)
		_GK.need_frame_packing = TRUE;
    return FALSE;
    }

static void
render_chart_pixmaps(GkrellmChart *cp)
	{
	GkrellmPiximage	piximage;
	GkrellmMargin	*m;
	gint			h, w;

	m = gkrellm_get_style_margins(cp->style);
	w = gdk_pixbuf_get_width(cp->bg_piximage->pixbuf)
				- _GK.frame_left_chart_overlap - _GK.frame_right_chart_overlap;
	h = gdk_pixbuf_get_height(cp->bg_piximage->pixbuf) - m->top - m->bottom;

	if (   (   m->top > 0 || m->bottom > 0
			|| _GK.frame_left_chart_overlap > 0
			|| _GK.frame_right_chart_overlap > 0
		   )
		&& w > 0 && h > 0
	   )
		{
		piximage.pixbuf = gdk_pixbuf_new_subpixbuf(cp->bg_piximage->pixbuf,
				_GK.frame_left_chart_overlap, m->top, w, h);
		piximage.border = cp->bg_piximage->border;
		gkrellm_border_adjust(&piximage.border,
				-_GK.frame_left_chart_overlap, -_GK.frame_right_chart_overlap,
				-m->top, -m->bottom);
		gkrellm_scale_piximage_to_pixmap(&piximage, &cp->bg_clean_pixmap,
					&cp->bg_mask, cp->w, cp->h);
		g_object_unref(G_OBJECT(piximage.pixbuf));
		}
	else
		gkrellm_scale_piximage_to_pixmap(cp->bg_piximage,
					&cp->bg_clean_pixmap, &cp->bg_mask, cp->w, cp->h);

	gkrellm_clone_pixmap(&cp->pixmap, &cp->bg_clean_pixmap);
	gkrellm_clone_pixmap(&cp->bg_pixmap, &cp->bg_clean_pixmap);
	gkrellm_clone_pixmap(&cp->bg_src_pixmap, &cp->bg_clean_pixmap);
	gkrellm_clone_pixmap(&cp->bg_text_pixmap, &cp->bg_clean_pixmap);
	}

void
gkrellm_chart_create(GtkWidget *vbox, GkrellmMonitor *mon, GkrellmChart *cp,
			GkrellmChartconfig **config)
	{
	GkrellmMargin	*m;
	GkrellmChartconfig *cf;
	gint			h, style_id;

	if (!vbox || !mon || !cp || !config)
		return;
	if (mon->privat->style_type == CHART_PANEL_TYPE)
		style_id = mon->privat->style_id;
	else
		style_id = DEFAULT_STYLE_ID;
	cp->style = gkrellm_chart_style(style_id);
	m = gkrellm_get_style_margins(cp->style);
	cp->monitor = (gpointer) mon;
	if (!cp->bg_piximage_override)
		{
		cp->bg_piximage = gkrellm_bg_chart_piximage(style_id);
		cp->bg_grid_piximage = gkrellm_bg_grid_piximage(style_id);
		}
	cp->bg_piximage_override = FALSE;
	cp->x = 0;
/*	cp->y = 0; */
	cp->w = _GK.chart_width;
	if (!*config)
		{
		*config = gkrellm_chartconfig_new0();
		(*config)->auto_grid_resolution = TRUE;		/* the default */
		(*config)->h = cp->h;					/* In case default */
		}
	cf = cp->config = *config;
	if (cf->h < 5)
		cf->h = 40;
	cp->h = cf->h;
	if (cf->grid_resolution < 1)
		cf->grid_resolution = 1;
	cp->cd_list = cp->config->cd_list;
	cp->config->chart_cd_list = &cp->cd_list;

	if (!cp->box)
		{
		cp->box = gtk_vbox_new(FALSE, 0);	/* not a hbox anymore !! */
		gtk_box_pack_start(GTK_BOX(vbox), cp->box, FALSE, FALSE, 0);

		cp->top_spacer.vbox = gtk_vbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(cp->box), cp->top_spacer.vbox,
				FALSE, FALSE, 0);
		cp->drawing_area = gtk_drawing_area_new();
		gtk_widget_set_events(cp->drawing_area, GDK_EXPOSURE_MASK
				| GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
				| GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK
				| GDK_POINTER_MOTION_MASK);
		gtk_box_pack_start(GTK_BOX(cp->box), cp->drawing_area,
				FALSE, FALSE, 0);
		cp->bottom_spacer.vbox = gtk_vbox_new(FALSE, 0);
		gtk_box_pack_start(GTK_BOX(cp->box), cp->bottom_spacer.vbox,
				FALSE, FALSE, 0);

		gtk_widget_show(cp->drawing_area);
		gtk_widget_show(cp->box);
		cp->shown = TRUE;
		gtk_widget_realize(cp->box);
		gtk_widget_realize(cp->drawing_area);
		cp->style_id = style_id;
		insert_in_chartlist(cp);
		cp->y_mapped = -1;
		g_signal_connect(G_OBJECT (cp->drawing_area), "size-allocate",
					G_CALLBACK(cb_chart_size_allocate), cp);
		}
	else
		free_chart_pixmaps(cp);

	gtk_widget_set_size_request(cp->drawing_area, cp->w, cp->h);

	cp->bg_sequence_id += 1;

	render_chart_pixmaps(cp);
	render_chart_margin_spacers(cp);

	h = gdk_pixbuf_get_height(cp->bg_grid_piximage->pixbuf);
	if (h > 2)
		h = 2;
	gkrellm_scale_piximage_to_pixmap(cp->bg_grid_piximage,
				&cp->bg_grid_pixmap, NULL, cp->w, h);

	cp->transparency = cp->style->transparency;
	_GK.any_transparency |= cp->transparency;

	gkrellm_set_draw_chart_function(cp, default_draw_chart_function, cp);
	cp->redraw_all = TRUE;
	render_default_data_pixmaps(cp);
	if (cp->shown)
		{
		gkrellm_monitor_height_adjust(cp->h + m->top + m->bottom);
		gkrellm_pack_side_frames();
		}
	}

void
gkrellm_refresh_chart(GkrellmChart *cp)
	{
	if (!cp)
		return;
	cp->redraw_all = TRUE;
	cp->maxval_auto = -1;
	if (cp->draw_chart)
		(*(cp->draw_chart))(cp->draw_chart_data);
	}

void
gkrellm_rescale_chart(GkrellmChart *cp)
	{
	if (!cp)
		return;
	scan_for_maxval(cp);
	gkrellm_refresh_chart(cp);
	}

/* =================================================================== */


static gint		map_125_table[] =
	{
	1, 2, 5,
	10, 20, 50,
	100, 200, 500,
	1000, 2000, 5000,
	10000, 20000, 50000,
	100000, 200000, 500000,
	1000000, 2000000, 5000000,
	10000000, 20000000, 50000000,
	100000000, 200000000, 500000000
	};

static gint		map_12357_table[] =
	{
	1, 2, 3, 5, 7,
	10, 15, 20, 30, 50, 70,
	100, 150, 200, 300, 500, 700,
	1000, 1500, 2000, 3000, 5000, 7000,
	10000, 15000, 20000, 30000, 50000, 70000,
	100000, 150000, 200000, 300000, 500000, 700000,
	1000000, 1500000, 2000000, 3000000, 5000000, 7000000,
	10000000, 15000000, 20000000, 30000000, 50000000, 70000000,
	100000000, 150000000, 200000000, 300000000, 500000000, 700000000
	};

gint
gkrellm_125_sequence(gint value, gboolean use125,
			gint low, gint high,
			gboolean snap_to_table, gboolean roundup)
	{
	gint    i, table_size;
	gint	*table;

	if (use125)
		{
		table = map_125_table;
		table_size = sizeof(map_125_table) / sizeof(gint);
		}
	else
		{
		table = map_12357_table;
		table_size = sizeof(map_12357_table) / sizeof(gint);
		}
	if (value < low)
		value = low;
	if (value > high)
		value = high;
	if (value < table[0])
		return table[0];
	if (value > table[table_size - 1])
		return table[table_size - 1];
	if (!snap_to_table && !roundup)
		{
		for (i = 0; i < table_size; ++i)
			{
/*g_debug("  mapping[%d] value=%d table=%d\n", i, value, table[i]); */
			if (value == table[i])
				return table[i];
			else if (value == table[i] - 1)
				return table[i - 1];
			else if (value == table[i] + 1)
				return table[i + 1];
			}
		}
	else if (snap_to_table && !roundup)
		{
		for (i = table_size - 1; i >= 0; --i)
			{
			if (value >= table[i])
				{
				value = table[i];
				break;
				}
			}
		}
	else if (snap_to_table && roundup)
		{
		for (i = 0; i < table_size; ++i)
			{
			if (value <= table[i])
				{
				value = table[i];
				break;
				}
			}
		}
	return value;
	}

static void
set_grid_resolution_spin_button(GkrellmChart *cp, gint res)
	{
	GtkSpinButton	*spin;

	if (!cp || !cp->config_window || !cp->config->grid_resolution_spin_button)
		return;
	spin = GTK_SPIN_BUTTON(cp->config->grid_resolution_spin_button);
	gtk_spin_button_set_value(spin, (gfloat) res);
	}

static void
cb_chart_grid_resolution(GtkWidget *adjustment, GkrellmChart *cp)
	{
	GtkSpinButton	*spin;
	GkrellmChartconfig		*cf;
	gint			res;
	gfloat			fres;

	_GK.config_modified = TRUE;
	cf = cp->config;
	spin = GTK_SPIN_BUTTON(cf->grid_resolution_spin_button);
	if (cf->map_sequence)
		{
		res = gtk_spin_button_get_value_as_int(spin);
		if (res != cf->grid_resolution)
			{
			res = gkrellm_125_sequence(res, cf->sequence_125,
						cf->low, cf->high, FALSE, FALSE);
			cf->grid_resolution = res;
			gtk_spin_button_set_value(spin, (gfloat) res);
			if (cf->cb_grid_resolution && !cf->cb_block)
				(*cf->cb_grid_resolution)(cf, cf->cb_grid_resolution_data);
			gkrellm_refresh_chart(cp);
			}
		}
	else
		{
		fres = gtk_spin_button_get_value(spin);
		if (cf->spin_factor > 0.0)
			fres *= cf->spin_factor;
		cf->grid_resolution = (gint) fres;
		if (cf->grid_resolution < 1)
			cf->grid_resolution = 1;
		if (cf->cb_grid_resolution && !cf->cb_block)
			(*cf->cb_grid_resolution)(cf, cf->cb_grid_resolution_data);
		gkrellm_refresh_chart(cp);
		}
	}


/* ---- GkrellmChartconfig functions ---- */
void
gkrellm_chartconfig_callback_block(GkrellmChartconfig *cf, gboolean block)
	{
	if (!cf)
		return;
	cf->cb_block = block;
	}

void
gkrellm_set_chartconfig_grid_resolution(GkrellmChartconfig *cf, gint res)
	{
	if (!cf || res <= 0)
		return;
	cf->grid_resolution = res;
	}

gint
gkrellm_get_chartconfig_grid_resolution(GkrellmChartconfig *cf)
	{
	if (!cf)
		return 0;
	return cf->grid_resolution;
	}

void
gkrellm_chartconfig_grid_resolution_connect(GkrellmChartconfig *cf,
			void (*func)(GkrellmChartconfig *, gpointer), gpointer data)
	{
	if (!cf)
		return;
	cf->cb_grid_resolution = func;
	cf->cb_grid_resolution_data = data;
	}

void
gkrellm_set_chartconfig_flags(GkrellmChartconfig *cf, gint flags)
	{
	if (!cf)
		return;
	cf->flags = flags;
	}

void
gkrellm_chartconfig_grid_resolution_adjustment(GkrellmChartconfig *cf,
			gboolean map_sequence, gfloat spin_factor,
			gfloat low, gfloat high, gfloat step0, gfloat step1, gint digits,
			gint width)
	{
	if (!cf)
		return;
	cf->map_sequence = map_sequence;
	if ((cf->width = width) == 0)
		cf->width = 70 + log(high / 100000) * 5;
	if (map_sequence)
		{
		cf->low = 1;
		cf->low = (gfloat) gkrellm_125_sequence((gint) low, cf->sequence_125,
							low, high, TRUE, FALSE);
		cf->high = (gfloat) gkrellm_125_sequence((gint) high,
							cf->sequence_125, low, high, TRUE, TRUE);
		cf->step0 = 1.0;
		cf->step1 = 1.0;
		cf->digits = 0;
		}
	else
		{
		cf->low = low;
		cf->high = high;
		cf->step0 = step0;
		cf->step1 = step1;
		cf->digits = digits;
		cf->spin_factor = spin_factor;
		}
	if (cf->spin_factor < 1.0)
		cf->spin_factor = 1.0;
	cf->adjustment_is_set = TRUE;
	}

void
gkrellm_chartconfig_grid_resolution_label(GkrellmChartconfig *cf, gchar *label)
	{
	if (!cf)
		return;
	gkrellm_dup_string(&cf->grid_resolution_label, label);
	}

void
gkrellm_set_chartconfig_auto_grid_resolution(GkrellmChartconfig *cf, gboolean ato)
	{
	if (cf)
		cf->auto_grid_resolution = ato;
	}

void
gkrellm_set_chartconfig_auto_resolution_stick(GkrellmChartconfig *cf, gboolean stick)
	{
	if (cf)
		cf->auto_resolution_stick = stick;
	}

void
gkrellm_set_chartconfig_sequence_125(GkrellmChartconfig *cf, gboolean seq)
	{
	if (cf)
		cf->sequence_125 = seq;
	}

void
gkrellm_set_chartconfig_fixed_grids(GkrellmChartconfig *cf, gint fixed_grids)
	{
	if (!cf || fixed_grids < 0 || fixed_grids > 5)
		return;
	cf->fixed_grids = fixed_grids;
	}

gint
gkrellm_get_chartconfig_fixed_grids(GkrellmChartconfig *cf)
	{
	if (!cf)
		return 0;
	return cf->fixed_grids;
	}

void
gkrellm_chartconfig_fixed_grids_connect(GkrellmChartconfig *cf,
			void (*func)(GkrellmChartconfig*, gpointer), gpointer data)
	{
	if (!cf)
		return;
	cf->cb_fixed_grids = func;
	cf->cb_fixed_grids_data = data;
	}

gint
gkrellm_get_chartconfig_height(GkrellmChartconfig *cf)
	{
	if (!cf)
		return 0;
	return cf->h;
	}

void
gkrellm_chartconfig_height_connect(GkrellmChartconfig *cf,
			void (*func)(GkrellmChartconfig*, gpointer), gpointer data)
	{
	if (!cf)
		return;
	cf->cb_height = func;
	cf->cb_height_data = data;
	}

void
gkrellm_set_chart_height(GkrellmChart *cp, gint h)
	{
	GtkWidget			*top_win = gkrellm_get_top_window();
	GtkSpinButton		*spin;
	GList				*list;
	GkrellmChartdata	*cd;
	GkrellmChartconfig	*cf;
	gint				dy;

	if (!cp || cp->h == h)
		return;
	dy = h - cp->h;
	cp->h = h;
	cp->config->h = h;
	render_default_data_pixmaps(cp);
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		g_object_unref(G_OBJECT(cd->data_bitmap));
		g_object_unref(G_OBJECT(cd->layer.pixmap));
		cd->data_bitmap = gdk_pixmap_new(gtk_widget_get_window(top_win), cp->w, cp->h, 1);
		cd->layer.pixmap = gdk_pixmap_new(gtk_widget_get_window(top_win), cp->w, cp->h, -1);
		}
	cp->bg_sequence_id += 1;
	render_chart_pixmaps(cp);

	cf = cp->config;
	if (cf->cb_height && !cf->cb_block)
		(*cf->cb_height)(cf, cf->cb_height_data);
	gtk_widget_set_size_request(cp->drawing_area, cp->w, cp->h);
	set_chartdata_split_heights(cp);
	if (cp->shown)
		{
		gkrellm_monitor_height_adjust(dy);
		gkrellm_pack_side_frames();
		gkrellm_refresh_chart(cp);
		}
	if (cp->config_window)
		{
		spin = GTK_SPIN_BUTTON(cf->height_spin_button);
		if (h != gtk_spin_button_get_value_as_int(spin))
			gtk_spin_button_set_value(spin, (gfloat) h);
		}
	}

gboolean
gkrellm_get_chartdata_hide(GkrellmChartdata *cd)
	{
	if (cd && cd->hide)
		return TRUE;
	return FALSE;
	}


/* =================================================================== */

static void
chart_config_window_close(GtkWidget *widget, GkrellmChart *cp)
	{
	if (cp->config_window)
		gtk_widget_destroy(cp->config_window);
	cp->config_window = NULL;
	}

void
gkrellm_chartconfig_window_destroy(GkrellmChart *cp)
	{
	chart_config_window_close(NULL, cp);
	}

static gint
chart_config_window_delete_event(GtkWidget *widget, GdkEvent *ev,
			gpointer data)
	{
	chart_config_window_close(widget, data);
	return FALSE;
	}

static void
set_resolution_menubar_items_sensitivity(GkrellmChartconfig *cf)
	{
	GtkWidget	*w;

	if (!cf->auto_resolution_ui_manager)
		return;

	w = gtk_ui_manager_get_widget(cf->auto_resolution_ui_manager,
                                      "/menubar/Control/AutoModeStickPeak");
	gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), cf->auto_resolution_stick);

	w = gtk_ui_manager_get_widget(cf->auto_resolution_ui_manager,
                                      "/menubar/Control/AutoModeRecalibrate");
	if (cf->auto_grid_resolution)
		gtk_widget_set_sensitive(w, TRUE);
	else
		gtk_widget_set_sensitive(w, FALSE);
	}

static void
cb_chart_height(GtkWidget *adjustment, GkrellmChart *cp)
	{
	GtkSpinButton	*spin;
	gint			h;

	_GK.config_modified = TRUE;
	spin = GTK_SPIN_BUTTON(cp->config->height_spin_button);
	h = gtk_spin_button_get_value_as_int(spin);
	gkrellm_set_chart_height(cp, h);
	}

static void
cb_chart_fixed_grids(GtkWidget *adjustment, GkrellmChart *cp)
	{
	GtkSpinButton		*spin;
	GkrellmChartconfig	*cf = cp->config;

	_GK.config_modified = TRUE;
	spin = GTK_SPIN_BUTTON(cf->fixed_grids_spin_button);
	cf->fixed_grids = gtk_spin_button_get_value_as_int(spin);
	if (cf->auto_grid_resolution)
		set_auto_grid_resolution(cp, cp->maxval_auto);
	if (cf->cb_fixed_grids && !cf->cb_block)
		(*cf->cb_fixed_grids)(cf, cf->cb_fixed_grids_data);

	set_resolution_menubar_items_sensitivity(cf);

	gkrellm_refresh_chart(cp);
	}

static void
cb_line_draw_style(GtkWidget *widget, GkrellmChartdata *cd)
	{
	_GK.config_modified = TRUE;
	cd->draw_style = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	gkrellm_rescale_chart(cd->chart);
	}

static void
cb_auto_resolution(GtkWidget *widget, GkrellmChart *cp)
	{
	GtkWidget			*button;
	GkrellmChartconfig	*cf = cp->config;

	_GK.config_modified = TRUE;
	cf->auto_grid_resolution = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));

	set_resolution_menubar_items_sensitivity(cf);
	button = cf->grid_resolution_spin_button;
	if (cf->auto_grid_resolution)
		gtk_widget_set_sensitive(button, FALSE);
	else
		gtk_widget_set_sensitive(button, TRUE);
	gkrellm_rescale_chart(cp);
	}

static void
cb_inverted_draw_mode(GtkWidget *widget, GkrellmChartdata *cd)
	{
	_GK.config_modified = TRUE;
	cd->inverted = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	gkrellm_rescale_chart(cd->chart);
	}

static void
cb_hide(GtkWidget *widget, GkrellmChartdata *cd)
	{
	_GK.config_modified = TRUE;
	cd->hide = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	set_chartdata_split_heights(cd->chart);
	gkrellm_rescale_chart(cd->chart);
	}

static void
cb_split_mode(GtkWidget *widget, GkrellmChartdata *cd)
	{
	_GK.config_modified = TRUE;
	cd->split_chart = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	gtk_widget_set_sensitive(cd->split_fraction_spin_button, cd->split_chart);
	set_chartdata_split_heights(cd->chart);
	gkrellm_rescale_chart(cd->chart);
	}

static void
cb_split_fraction(GtkWidget *adjustment, GkrellmChartdata *cd)
	{
	GtkSpinButton	*spin;

	_GK.config_modified = TRUE;
	spin = GTK_SPIN_BUTTON(cd->split_fraction_spin_button);
	cd->split_fraction = gtk_spin_button_get_value(spin);
	set_chartdata_split_heights(cd->chart);
	gkrellm_rescale_chart(cd->chart);
	}


/* =================================================================== */

static void
cb_seq_control(GtkRadioAction *action, GtkRadioAction *current, GkrellmChart *cp )
{
    GkrellmChartconfig *cf = cp->config;

    if (cf->sequence_125 == gtk_radio_action_get_current_value(action))
        return;
    cf->sequence_125 = gtk_radio_action_get_current_value(action);

    cf->grid_resolution = gkrellm_125_sequence(cf->grid_resolution,
                                               cf->sequence_125, cf->low, cf->high, TRUE, FALSE);
    set_grid_resolution_spin_button(cp, cf->grid_resolution);
}

static void
cb_auto_stick_control(GtkToggleAction *action, GkrellmChart *cp )
{
    GkrellmChartconfig *cf = cp->config;

    cf->auto_resolution_stick = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
    cp->maxval_auto_base = 0;
    gkrellm_refresh_chart(cp);
}

static void
cb_auto_res_control(GtkAction *action, GkrellmChart *cp )
{
    cp->maxval_auto_base = 0;
    cp->maxval_peak = 0;
    gkrellm_refresh_chart(cp);
}

static const char *auto_res_control_items = "\
<ui>\
  <menubar>\
  <menu name=\"Control\" action=\"ControlAction\">\
    <separator/>\
    <menuitem name=\"AutoModeRecalibrate\" action=\"AutoModeRecalibrateAction\"/>\
    <menuitem name=\"AutoModeStickPeak\" action=\"AutoModeStickPeakAction\"/>\
    <menu name=\"SequenceMenu\" action=\"SequenceMenuAction\">\
      <menuitem name=\"Seq125\" action=\"Seq125Action\"/>\
      <menuitem name=\"Seq1357\" action=\"Seq1357Action\"/>\
    </menu>\
    <separator/>\
  </menu>\
  </menubar>\
</ui>\
";

static GtkActionEntry auto_res_control_entries[] =
{
    { "ControlAction", NULL, N_("Control"),
      NULL, NULL, G_CALLBACK(NULL) },
    { "SequenceMenuAction", NULL, N_("Sequence..."),
      NULL, NULL, G_CALLBACK(NULL) },
    { "AutoModeRecalibrateAction", NULL, N_("Auto mode recalibrate"),
      NULL, NULL, G_CALLBACK(cb_auto_res_control) },
};
static guint n_auto_res_control_entries = G_N_ELEMENTS (auto_res_control_entries);

static GtkToggleActionEntry auto_res_control_toggle_entries[] =
{
    { "AutoModeStickPeakAction", NULL, N_("Auto mode sticks at peak value"),
      NULL, NULL, G_CALLBACK(cb_auto_stick_control), FALSE },
};
static guint n_auto_res_control_toggle_entries = G_N_ELEMENTS (auto_res_control_toggle_entries);

static GtkRadioActionEntry auto_res_control_radio_entries[] =
{
    { "Seq125Action", NULL, N_("1 2 5"),
      NULL, NULL, 1 },
    { "Seq1357Action", NULL, N_("1 1.5 2 3 5 7"),
      NULL, NULL, 0 },
};
static guint n_auto_res_control_radio_entries = G_N_ELEMENTS (auto_res_control_radio_entries);

static void
auto_resolution_control_menubar(GtkWidget **menubar, GkrellmChart *cp)
	{
        GtkUIManager *ui_manager;
        GtkActionGroup *action_group;
	GkrellmChartconfig		*cf = cp->config;
        GError *error;

        action_group = gtk_action_group_new ("ControlActions");
        gtk_action_group_add_actions (action_group, auto_res_control_entries,
                                      n_auto_res_control_entries, cp);
        gtk_action_group_add_toggle_actions (action_group, auto_res_control_toggle_entries,
                                             n_auto_res_control_toggle_entries, cp);
        gtk_action_group_add_radio_actions (action_group, auto_res_control_radio_entries,
                                            n_auto_res_control_radio_entries,
                                            !!cf->sequence_125, G_CALLBACK(cb_seq_control),
                                            cp);
        ui_manager = gtk_ui_manager_new ();
        error = NULL;
        gtk_ui_manager_add_ui_from_string (ui_manager, auto_res_control_items,
                                           strlen(auto_res_control_items), &error);
        if (error)
        {
            g_message ("building menus failed: %s", error->message);
            g_error_free (error);
            return;
        }
        gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
	cf->auto_resolution_ui_manager = ui_manager;
	set_resolution_menubar_items_sensitivity(cf);

	if (menubar)
		*menubar = gtk_ui_manager_get_widget(ui_manager, "/menubar");
	}

void
gkrellm_chartconfig_window_create(GkrellmChart *cp)
	{
	GtkWidget			*main_vbox, *vbox, *vbox1, *vbox2, *hbox;
	GtkWidget			*button;
	GList				*list;
	GkrellmChartconfig	*cf;
	GkrellmChartdata	*cd;
	GkrellmPanel		*p;
	gchar				*s;

	if (!cp || _GK.no_config)
		return;
	if (cp->config_window)
		{
		gtk_window_present(GTK_WINDOW(cp->config_window));
		return;
		}
	cp->config_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect(G_OBJECT(cp->config_window), "delete-event",
			G_CALLBACK(chart_config_window_delete_event), cp);

	p = cp->panel;
	cf = cp->config;
	if (p && p->label)
		s = p->label->string;
	else
		s = NULL;
	gtk_window_set_title(GTK_WINDOW(cp->config_window),
				_("GKrellM Chart Config"));
	gtk_window_set_wmclass(GTK_WINDOW(cp->config_window),
				"Gkrellm_conf", "Gkrellm");

	main_vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(cp->config_window), 4);
	gtk_container_add(GTK_CONTAINER(cp->config_window), main_vbox);
	vbox = gkrellm_gtk_framed_vbox(main_vbox, s, 4, FALSE, 4, 3);

	hbox = gtk_hbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	for (list = cp->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		if ((cd->flags & CHARTDATA_NO_CONFIG) == CHARTDATA_NO_CONFIG)
			continue;
		vbox1 = gkrellm_gtk_framed_vbox(hbox, cd->label, 2, TRUE, 2, 2);

		if (!(cd->flags & CHARTDATA_NO_CONFIG_DRAW_STYLE))
			{
			gkrellm_gtk_check_button(vbox1, &button, cd->draw_style, FALSE, 0,
					_("Line style"));
			g_signal_connect(G_OBJECT(button), "toggled",
					G_CALLBACK(cb_line_draw_style), cd);
			}
		if (!(cd->flags & CHARTDATA_NO_CONFIG_INVERTED))
			{
			gkrellm_gtk_check_button(vbox1, &button, cd->inverted, FALSE, 0,
					_("Inverted"));
			g_signal_connect(G_OBJECT(button), "toggled",
					G_CALLBACK(cb_inverted_draw_mode), cd);
			}
		if (list != cp->cd_list && !(cd->flags & CHARTDATA_NO_CONFIG_SPLIT))
			{
			vbox2 = gkrellm_gtk_framed_vbox(vbox1, NULL, 2, FALSE, 2, 2);
			gkrellm_gtk_check_button(vbox2, &button, cd->split_chart, FALSE, 0,
					_("Split view"));
			g_signal_connect(G_OBJECT(button), "toggled",
					G_CALLBACK(cb_split_mode), cd);
			gkrellm_gtk_spin_button(vbox2, &button, cd->split_fraction,
					0.05, 0.95, 0.01, 0.05, 2, 55,
					cb_split_fraction, cd, FALSE, "");
			gtk_widget_set_sensitive(button, cd->split_chart);
			cd->split_fraction_spin_button = button;
			}
		if (cd->flags & CHARTDATA_ALLOW_HIDE)
			{
			gkrellm_gtk_check_button(vbox1, &button, cd->hide, FALSE, 0,
					_("Hide"));
			g_signal_connect(G_OBJECT(button), "toggled",
					G_CALLBACK(cb_hide), cd);
			}
		}

	cf->auto_resolution_control_menubar = NULL;
	cf->auto_resolution_ui_manager = NULL;
	cf->grid_resolution_spin_button = NULL;
	cf->fixed_grids_spin_button = NULL;

	if (cf->adjustment_is_set)
		{
		gfloat	value;

		vbox1 = gkrellm_gtk_framed_vbox(vbox, _("Resolution per Grid"),
					2, FALSE, 2, 2);
		if (cf->map_sequence)
			value = (gfloat) cf->grid_resolution;
		else
			value = cf->grid_resolution / cf->spin_factor;
		gkrellm_gtk_spin_button(vbox1, &button, value,
			cf->low, cf->high, cf->step0, cf->step1, cf->digits, cf->width,
			cb_chart_grid_resolution, cp, FALSE, cf->grid_resolution_label);
		cf->grid_resolution_spin_button = button;
		if (cp->config->auto_grid_resolution)
			gtk_widget_set_sensitive(button, FALSE);

		if (!(cp->config->flags & NO_CONFIG_AUTO_GRID_RESOLUTION))
			{
			hbox = gtk_hbox_new (FALSE, 0);
			gtk_container_set_border_width(GTK_CONTAINER(hbox), 2);
			gtk_container_add(GTK_CONTAINER(vbox1), hbox);
			gkrellm_gtk_check_button(hbox, &button,
					cp->config->auto_grid_resolution, TRUE, 0,
					_("Auto"));
			g_signal_connect(G_OBJECT(button), "toggled",
					G_CALLBACK(cb_auto_resolution), cp);

			auto_resolution_control_menubar(
					&cf->auto_resolution_control_menubar, cp);
			gtk_box_pack_start(GTK_BOX(hbox),
				cf->auto_resolution_control_menubar, FALSE, TRUE, 10);
			}
		}
	if (!(cp->config->flags & NO_CONFIG_FIXED_GRIDS))
		{
		vbox1 = gkrellm_gtk_framed_vbox(vbox, _("Number of Grids"), 2, FALSE,
				2, 2);
		gkrellm_gtk_spin_button(vbox1, &button, (gfloat) cf->fixed_grids,
				0, 5, 1.0, 1.0, 0, 50,
				cb_chart_fixed_grids, cp, FALSE,
				_("0: Auto    1-5: Constant"));
		cf->fixed_grids_spin_button = button;
		}

	vbox1 = gkrellm_gtk_framed_vbox(vbox, NULL, 2, FALSE, 2, 2);
	gkrellm_gtk_spin_button(vbox1, &button, (gfloat) cp->h,
			(gfloat) _GK.chart_height_min, (gfloat) _GK.chart_height_max,
			5.0, 10.0, 0, 50,
			cb_chart_height, cp, FALSE,
			_("Chart height"));
	cf->height_spin_button = button;

	hbox = gtk_hbutton_box_new();
	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
	gtk_box_set_spacing(GTK_BOX(hbox), 5);
	gtk_box_pack_start(GTK_BOX(main_vbox), hbox, FALSE, FALSE, 0);

	button = gtk_button_new_from_stock(GTK_STOCK_OK);
	gtk_widget_set_can_default(button, 1);
	g_signal_connect(G_OBJECT(button), "clicked",
			G_CALLBACK(chart_config_window_close), cp);
    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 15);
	gtk_widget_grab_default(button);

	gtk_widget_show_all(cp->config_window);
	}

void
gkrellm_save_chartconfig(FILE *f, GkrellmChartconfig *cf, gchar *mon_keyword,
			gchar *name)
	{
	GList			*list;
	GkrellmChartdata *cd;

	if (!f || !cf || !mon_keyword)
		return;
	if (name)
		fprintf(f, "%s %s %s ", mon_keyword, GKRELLM_CHARTCONFIG_KEYWORD,name);
	else
		fprintf(f, "%s %s ", mon_keyword, GKRELLM_CHARTCONFIG_KEYWORD);
	fprintf(f, "%d %d %d %d %d %d", cf->h, cf->grid_resolution,
				cf->fixed_grids, cf->auto_grid_resolution,
				cf->auto_resolution_stick, cf->sequence_125);
	for (list = cf->cd_list; list; list = list->next)
		{
		cd = (GkrellmChartdata *) list->data;
		fprintf(f, " : %d %d %d %d %.0f",
				cd->draw_style, cd->inverted, cd->hide,
				cd->split_chart, cd->split_fraction * GKRELLM_FLOAT_FACTOR);
		}
	fprintf(f, "\n");
	}

void
gkrellm_load_chartconfig(GkrellmChartconfig **config, gchar *string,
			gint max_cd)
	{
	GList				*list;
	GkrellmChartdata	*cd;
	GkrellmChartconfig	*cf;
	gchar				*s;
	gint				index = 0;

	if (!config || !string)
		return;
	if (!*config)
		{
		*config = gkrellm_chartconfig_new0();
		(*config)->auto_grid_resolution = TRUE;		/* the default */
		}
	cf = *config;
	sscanf(string, "%d %d %d %d %d %d", &cf->h, &cf->grid_resolution,
				&cf->fixed_grids, &cf->auto_grid_resolution,
				&cf->auto_resolution_stick, &cf->sequence_125);
	for (s = strchr(string, (int) ':'); s ; s = strchr(s, (int) ':'))
		{
		++s;
		list = g_list_nth(cf->cd_list, index++);
		if (!list)
			{
			cd = g_new0(GkrellmChartdata, 1);
			cd->split_fraction = 0.5;
			cf->cd_list = g_list_append(cf->cd_list, cd);
			}
		else
			cd = (GkrellmChartdata *) list->data;
		sscanf(s, "%d %d %d %d %f",
				&cd->draw_style, &cd->inverted, &cd->hide,
				&cd->split_chart, &cd->split_fraction);
		cd->split_fraction /= _GK.float_factor;
		if (cd->split_fraction <= 0.01 || cd->split_fraction >= 0.99)
			cd->split_fraction = 0.5;

		cf->config_loaded = TRUE;
		if (max_cd && index >= max_cd)
			break;
		}
	}
